Skip to main content

rustledger_plugin/native/plugins/
no_duplicates.rs

1//! Hash-based duplicate transaction detection.
2
3use crate::types::{DirectiveData, PluginError, PluginInput, PluginOutput, TransactionData};
4
5use super::super::NativePlugin;
6
7/// Plugin that detects duplicate transactions based on hash.
8pub struct NoDuplicatesPlugin;
9
10impl NativePlugin for NoDuplicatesPlugin {
11    fn name(&self) -> &'static str {
12        "noduplicates"
13    }
14
15    fn description(&self) -> &'static str {
16        "Hash-based duplicate transaction detection"
17    }
18
19    fn process(&self, input: PluginInput) -> PluginOutput {
20        use std::collections::HashSet;
21        use std::collections::hash_map::DefaultHasher;
22        use std::hash::{Hash, Hasher};
23
24        fn hash_transaction(date: &str, txn: &TransactionData) -> u64 {
25            let mut hasher = DefaultHasher::new();
26            date.hash(&mut hasher);
27            txn.narration.hash(&mut hasher);
28            txn.payee.hash(&mut hasher);
29            for posting in &txn.postings {
30                posting.account.hash(&mut hasher);
31                if let Some(units) = &posting.units {
32                    units.number.hash(&mut hasher);
33                    units.currency.hash(&mut hasher);
34                }
35            }
36            hasher.finish()
37        }
38
39        let mut seen: HashSet<u64> = HashSet::new();
40        let mut errors = Vec::new();
41
42        for wrapper in &input.directives {
43            if let DirectiveData::Transaction(txn) = &wrapper.data {
44                let hash = hash_transaction(&wrapper.date, txn);
45                if !seen.insert(hash) {
46                    errors.push(PluginError::error(format!(
47                        "Duplicate transaction: {} \"{}\"",
48                        wrapper.date, txn.narration
49                    )));
50                }
51            }
52        }
53
54        PluginOutput {
55            directives: input.directives,
56            errors,
57        }
58    }
59}