rustledger_plugin/native/plugins/
no_duplicates.rs1use crate::types::{DirectiveData, PluginError, PluginInput, PluginOutput, TransactionData};
4
5use super::super::NativePlugin;
6
7pub 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}