|
@@ -1,12 +1,21 @@
|
|
|
use std::collections::BTreeMap;
|
|
|
|
|
|
-use chumsky::span::Span as CSpan;
|
|
|
use ariadne::Span as ASpan;
|
|
|
+use chumsky::span::Span as CSpan;
|
|
|
|
|
|
use crate::data::{DataError, Decimal, Root, Span, Spanned, UnitName};
|
|
|
|
|
|
+#[derive(Default, PartialEq, PartialOrd)]
|
|
|
+pub enum CheckLevel {
|
|
|
+ /// Check individual transactions
|
|
|
+ WellFormed,
|
|
|
+ /// Check relations between transactions
|
|
|
+ #[default]
|
|
|
+ Consistent,
|
|
|
+}
|
|
|
+
|
|
|
fn check_equal_sum(root: &Root) -> Result<(), DataError> {
|
|
|
- log::debug!("Checking for equal sums in monounit ledger entries...");
|
|
|
+ log::trace!("Checking for equal sums in monounit ledger entries...");
|
|
|
|
|
|
for le in root.all_ledger_data() {
|
|
|
let Some(tx) = le.as_transaction() else {
|
|
@@ -42,7 +51,7 @@ fn check_equal_sum(root: &Root) -> Result<(), DataError> {
|
|
|
}
|
|
|
|
|
|
fn check_precision(root: &Root) -> Result<(), DataError> {
|
|
|
- log::debug!("Checking for precision errors in ledger entries...");
|
|
|
+ log::trace!("Checking for precision errors in ledger entries...");
|
|
|
|
|
|
for le in root.all_ledger_data() {
|
|
|
let Some(tx) = le.as_transaction() else {
|
|
@@ -96,6 +105,8 @@ fn check_precision(root: &Root) -> Result<(), DataError> {
|
|
|
}
|
|
|
|
|
|
fn check_balances(root: &Root) -> Result<(), DataError> {
|
|
|
+ log::trace!("Checking balance consistency...");
|
|
|
+
|
|
|
for account in root.account_names() {
|
|
|
let Some(ledger) = root.ledger_data_for(account) else {
|
|
|
continue;
|
|
@@ -104,31 +115,30 @@ fn check_balances(root: &Root) -> Result<(), DataError> {
|
|
|
let mut running_balance = BTreeMap::<UnitName, Spanned<Decimal>>::new();
|
|
|
for txn in ledger {
|
|
|
let change = txn.change_for(account).unwrap();
|
|
|
- let bal = running_balance.entry(*change.unit).or_insert_with(|| Spanned::new(Decimal::default(), Span::default()));
|
|
|
+ let bal = running_balance
|
|
|
+ .entry(*change.unit)
|
|
|
+ .or_insert_with(|| Spanned::new(Decimal::default(), Span::default()));
|
|
|
let last_span = bal.span();
|
|
|
bal.0 = bal.checked_add(*change.amount).unwrap();
|
|
|
bal.1 = change.1;
|
|
|
|
|
|
if let Some(sbal) = change.balance.as_ref() {
|
|
|
if **sbal != bal.0 {
|
|
|
- let report =
|
|
|
- ariadne::Report::build(ariadne::ReportKind::Error, txn.span())
|
|
|
- .with_label(ariadne::Label::new(sbal.span()).with_message(format!(
|
|
|
- "Calculated balance is {} {}, specified balance is {} {}",
|
|
|
- bal, change.unit, sbal, change.unit
|
|
|
- )));
|
|
|
- let report =
|
|
|
- if !last_span.is_empty() {
|
|
|
- report.with_label(ariadne::Label::new(last_span).with_message(format!("Last balance is from here")))
|
|
|
- } else {
|
|
|
- report
|
|
|
- };
|
|
|
-
|
|
|
- return Err(
|
|
|
+ let report = ariadne::Report::build(ariadne::ReportKind::Error, txn.span())
|
|
|
+ .with_label(ariadne::Label::new(sbal.span()).with_message(format!(
|
|
|
+ "Calculated balance is {} {}, specified balance is {} {}",
|
|
|
+ bal, change.unit, sbal, change.unit
|
|
|
+ )));
|
|
|
+ let report = if !last_span.is_empty() {
|
|
|
+ report.with_label(
|
|
|
+ ariadne::Label::new(last_span)
|
|
|
+ .with_message(format!("Last balance is from here")),
|
|
|
+ )
|
|
|
+ } else {
|
|
|
report
|
|
|
- .finish()
|
|
|
- .into(),
|
|
|
- );
|
|
|
+ };
|
|
|
+
|
|
|
+ return Err(report.finish().into());
|
|
|
}
|
|
|
}
|
|
|
}
|
|
@@ -136,9 +146,15 @@ fn check_balances(root: &Root) -> Result<(), DataError> {
|
|
|
Ok(())
|
|
|
}
|
|
|
|
|
|
-pub fn run_checks(root: &mut Root) -> Result<(), DataError> {
|
|
|
- check_precision(root)?;
|
|
|
- check_equal_sum(root)?;
|
|
|
- check_balances(root)?;
|
|
|
+pub fn run_checks(root: &mut Root, level: CheckLevel) -> Result<(), DataError> {
|
|
|
+ if level >= CheckLevel::WellFormed {
|
|
|
+ log::debug!("Running transaction well-formedness checks...");
|
|
|
+ check_precision(root)?;
|
|
|
+ check_equal_sum(root)?;
|
|
|
+ }
|
|
|
+ if level >= CheckLevel::Consistent {
|
|
|
+ log::debug!("Running ledger consistency checks...");
|
|
|
+ check_balances(root)?;
|
|
|
+ }
|
|
|
Ok(())
|
|
|
}
|