tackler_core/model/
txn_data.rs1use itertools::Itertools;
7
8use crate::kernel::Predicate;
9use crate::kernel::hash::Hash;
10use crate::model::{TxnRefs, Txns, transaction};
11use crate::tackler;
12use tackler_api::filters::FilterDefinition;
13use tackler_api::metadata::items::{MetadataItem, TxnFilterDescription, TxnSetChecksum};
14use tackler_api::metadata::{Checksum, Metadata};
15
16#[derive(Debug)]
17pub struct TxnData {
18 metadata: Option<Metadata>,
19 txns: Txns,
20 hash: Option<Hash>,
21}
22
23pub struct TxnSet<'a> {
24 pub(crate) metadata: Option<Metadata>,
25 pub(crate) txns: TxnRefs<'a>,
26}
27
28impl TxnSet<'_> {
29 #[must_use]
30 pub fn metadata(&self) -> Option<&Metadata> {
31 self.metadata.as_ref()
32 }
33 #[must_use]
34 pub fn is_empty(&self) -> bool {
35 self.txns.is_empty()
36 }
37}
38
39impl TxnData {
40 #[must_use]
41 pub fn len(&self) -> usize {
42 self.txns.len()
43 }
44
45 #[must_use]
46 pub fn is_empty(&self) -> bool {
47 self.txns.is_empty()
48 }
49
50 pub fn from(mdi_opt: Option<MetadataItem>, txns: Txns, hash: &Option<Hash>) -> TxnData {
51 let metadata = mdi_opt.map(Metadata::from_mdi);
52
53 let mut t = txns;
54 t.sort_by(transaction::ord_by_txn);
55
56 TxnData {
57 metadata,
58 txns: t,
59 hash: hash.clone(),
60 }
61 }
62
63 pub fn append(&mut self, txn_data: &mut TxnData) -> Result<&mut Self, tackler::Error> {
73 self.txns.append(&mut txn_data.txns);
74 let metadata =
75 TxnData::make_metadata(self.hash.as_ref(), None, &self.txns.iter().collect())?;
76 self.metadata = Some(metadata);
77 Ok(self)
78 }
79
80 fn make_metadata(
81 hash_opt: Option<&Hash>,
82 metadata_opt: Option<&Metadata>,
83 txns: &TxnRefs<'_>,
84 ) -> Result<Metadata, tackler::Error> {
85 let mut metadata = match metadata_opt {
86 Some(md) => Metadata::from_metadata(md),
87 None => Metadata::new(),
88 };
89
90 if let Some(hash) = hash_opt {
91 let new_tsc_mdi = MetadataItem::TxnSetChecksum(TxnSetChecksum {
92 size: txns.len(),
93 hash: calc_txn_checksum(txns, hash)?,
94 });
95
96 metadata.push(new_tsc_mdi);
97 }
98
99 Ok(metadata)
100 }
101
102 pub fn filter(&self, tf: &FilterDefinition) -> Result<TxnSet<'_>, tackler::Error> {
105 let refvec: TxnRefs<'_> = self.txns.iter().filter(|txn| tf.eval(txn)).collect();
106
107 let mut metadata =
108 TxnData::make_metadata(self.hash.as_ref(), self.metadata.as_ref(), &refvec)?;
109 let filter_mdi = MetadataItem::TxnFilterDescription(TxnFilterDescription::from(tf.clone()));
110 metadata.push(filter_mdi);
111
112 Ok(TxnSet {
113 metadata: Some(metadata),
114 txns: refvec,
115 })
116 }
117
118 pub fn get_all(&self) -> Result<TxnSet<'_>, tackler::Error> {
121 let txns: TxnRefs<'_> = self.txns.iter().collect();
122
123 let metadata = if self.hash.is_some() || self.metadata.is_some() {
124 Some(TxnData::make_metadata(
125 self.hash.as_ref(),
126 self.metadata.as_ref(),
127 &txns,
128 )?)
129 } else {
130 None
131 };
132
133 Ok(TxnSet { metadata, txns })
134 }
135}
136
137fn calc_txn_checksum(txns: &TxnRefs<'_>, hasher: &Hash) -> Result<Checksum, tackler::Error> {
138 let uuids: Result<Vec<String>, tackler::Error> = txns
139 .iter()
140 .map(|txn| if let Some(uuid) = txn.header.uuid { Ok(uuid.to_string()) } else {
141 let msg = "Txn without UUID. Txn UUID is mandatory with transaction set checksum calculation.";
142 Err(msg.into())
143 })
144 .collect();
145
146 let mut u = uuids?;
147 u.sort();
148
149 let dups: Vec<String> = u.iter().duplicates().cloned().collect();
150 if !dups.is_empty() {
151 let dups_count = dups.len();
152 let msg = if dups_count < 10 {
153 format!(
154 "Found {} duplicate txn uuids with txn set checksum.\nDuplicate ids are:\n{}",
155 dups.len(),
156 dups.join(",\n")
157 )
158 } else {
159 format!(
160 "Found {} duplicate txn uuids with txn set checksum.\nFirst ten duplicate ids are:\n{}",
161 dups.len(),
162 dups[0..10].join(",\n")
163 )
164 };
165 return Err(msg.into());
166 }
167
168 let cs = hasher.checksum(&u, "\n".as_bytes());
169 Ok(cs)
170}
171
172#[cfg(test)]
173mod tests {
174 use uuid::Uuid;
175
176 #[test]
177 fn uuid_as_lower_case() {
179 let uuid_ref = "e274c99e-1ebb-45e8-832d-58caf54ed95f";
180 let uuid_mixed = "E274C99E-1ebb-45e8-832d-58Caf54Ed95f";
181 let uuid_upper = "E274C99E-1EBB-45E8-832D-58CAF54ED95F";
182
183 assert_eq!(
184 Uuid::parse_str(uuid_ref).unwrap().to_string(),
185 uuid_ref
186 );
187 assert_eq!(
188 Uuid::parse_str(uuid_mixed).unwrap().to_string(),
189 uuid_ref
190 );
191 assert_eq!(
192 Uuid::parse_str(uuid_upper).unwrap().to_string(),
193 uuid_ref
194 );
195 }
196}