1use std::collections::BTreeMap;
2use std::collections::BTreeSet;
3use std::fmt::Write as _;
4use std::fs;
5use std::fs::File;
6use std::path::Path;
7
8use serde_json;
9
10pub type ArtifactId = String;
11pub type FileContent = String;
12
13#[derive(Debug, Default)]
16pub struct FakeAggregatorData {
17 epoch_settings: FileContent,
18
19 certificates_list: FileContent,
20 individual_certificates: BTreeMap<ArtifactId, FileContent>,
21
22 snapshots_list: FileContent,
23 individual_snapshots: BTreeMap<ArtifactId, FileContent>,
24
25 mithril_stake_distributions_list: FileContent,
26 individual_mithril_stake_distributions: BTreeMap<ArtifactId, FileContent>,
27
28 cardano_transaction_snapshots_list: FileContent,
29 individual_cardano_transaction_snapshots: BTreeMap<ArtifactId, FileContent>,
30 cardano_transaction_proofs: BTreeMap<ArtifactId, FileContent>,
31
32 cardano_stake_distributions_list: FileContent,
33 individual_cardano_stake_distributions: BTreeMap<ArtifactId, FileContent>,
34
35 cardano_database_snapshots_list: FileContent,
36 individual_cardano_database_snapshots: BTreeMap<ArtifactId, FileContent>,
37}
38
39impl FakeAggregatorData {
40 pub fn load_from_folder(folder: &Path) -> Self {
41 let mut data = FakeAggregatorData::default();
42
43 for entry in list_json_files_in_folder(folder) {
44 let filename = entry.file_name().to_string_lossy().to_string();
45 let file_content = fs::read_to_string(entry.path()).unwrap_or_else(|_| {
46 panic!(
47 "Could not read file content, file_path: {}",
48 entry.path().display()
49 )
50 });
51
52 match filename.as_str() {
53 "epoch-settings.json" => {
54 data.epoch_settings = file_content;
55 }
56 "mithril-stake-distributions-list.json" => {
57 data.mithril_stake_distributions_list = file_content;
58 }
59 "snapshots-list.json" => {
60 data.snapshots_list = file_content;
61 }
62 "cardano-stake-distributions-list.json" => {
63 data.cardano_stake_distributions_list = file_content;
64 }
65 "cardano-databases-list.json" => {
66 data.cardano_database_snapshots_list = file_content;
67 }
68 "certificates-list.json" => {
69 data.certificates_list = file_content;
70 }
71 "ctx-snapshots-list.json" => {
72 data.cardano_transaction_snapshots_list = file_content;
73 }
74 "mithril-stake-distributions.json" => {
75 data.individual_mithril_stake_distributions =
76 Self::read_artifacts_json_file(&entry.path());
77 }
78 "snapshots.json" => {
79 data.individual_snapshots = Self::read_artifacts_json_file(&entry.path());
80 }
81 "cardano-stake-distributions.json" => {
82 data.individual_cardano_stake_distributions =
83 Self::read_artifacts_json_file(&entry.path());
84 }
85 "cardano-databases.json" => {
86 data.individual_cardano_database_snapshots =
87 Self::read_artifacts_json_file(&entry.path());
88 }
89 "certificates.json" => {
90 data.individual_certificates = Self::read_artifacts_json_file(&entry.path());
91 }
92 "ctx-snapshots.json" => {
93 data.individual_cardano_transaction_snapshots =
94 Self::read_artifacts_json_file(&entry.path());
95 }
96 "ctx-proofs.json" => {
97 data.cardano_transaction_proofs = Self::read_artifacts_json_file(&entry.path());
98 }
99 _ => {}
101 }
102 }
103
104 data
105 }
106
107 pub fn generate_code_for_ids(self) -> String {
108 Self::assemble_code(
109 &[
110 generate_ids_array(
111 "snapshot_digests",
112 BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
113 ),
114 generate_ids_array(
115 "mithril_stake_distribution_hashes",
116 BTreeSet::from_iter(
117 self.individual_mithril_stake_distributions.keys().cloned(),
118 ),
119 ),
120 generate_ids_array(
121 "cardano_stake_distribution_hashes",
122 BTreeSet::from_iter(
123 self.individual_cardano_stake_distributions.keys().cloned(),
124 ),
125 ),
126 generate_ids_array(
127 "cardano_stake_distribution_epochs",
128 BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
129 &self.individual_cardano_stake_distributions,
130 )),
131 ),
132 generate_ids_array(
133 "cardano_database_snapshot_hashes",
134 BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
135 ),
136 generate_ids_array(
137 "certificate_hashes",
138 BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
139 ),
140 generate_ids_array(
141 "cardano_transaction_snapshot_hashes",
142 BTreeSet::from_iter(
143 self.individual_cardano_transaction_snapshots
144 .keys()
145 .cloned(),
146 ),
147 ),
148 generate_ids_array(
149 "proof_transaction_hashes",
150 BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
151 ),
152 ],
153 false,
154 )
155 }
156
157 pub fn generate_code_for_all_data(self) -> String {
158 Self::assemble_code(
159 &[
160 generate_list_getter("epoch_settings", self.epoch_settings),
161 generate_ids_array(
162 "snapshot_digests",
163 BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
164 ),
165 generate_artifact_getter("snapshots", self.individual_snapshots),
166 generate_list_getter("snapshot_list", self.snapshots_list),
167 generate_ids_array(
168 "mithril_stake_distribution_hashes",
169 BTreeSet::from_iter(
170 self.individual_mithril_stake_distributions.keys().cloned(),
171 ),
172 ),
173 generate_artifact_getter(
174 "mithril_stake_distributions",
175 self.individual_mithril_stake_distributions,
176 ),
177 generate_list_getter(
178 "mithril_stake_distribution_list",
179 self.mithril_stake_distributions_list,
180 ),
181 generate_ids_array(
182 "cardano_stake_distribution_hashes",
183 BTreeSet::from_iter(
184 self.individual_cardano_stake_distributions.keys().cloned(),
185 ),
186 ),
187 generate_ids_array(
188 "cardano_stake_distribution_epochs",
189 BTreeSet::from_iter(extract_cardano_stake_distribution_epochs(
190 &self.individual_cardano_stake_distributions,
191 )),
192 ),
193 generate_artifact_getter(
194 "cardano_stake_distributions",
195 self.individual_cardano_stake_distributions,
196 ),
197 generate_list_getter(
198 "cardano_stake_distribution_list",
199 self.cardano_stake_distributions_list,
200 ),
201 generate_ids_array(
202 "certificate_hashes",
203 BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
204 ),
205 generate_ids_array(
206 "cardano_database_snapshot_hashes",
207 BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
208 ),
209 generate_artifact_getter(
210 "cardano_database_snapshots",
211 self.individual_cardano_database_snapshots,
212 ),
213 generate_list_getter(
214 "cardano_database_snapshot_list",
215 self.cardano_database_snapshots_list,
216 ),
217 generate_artifact_getter("certificates", self.individual_certificates),
218 generate_list_getter("certificate_list", self.certificates_list),
219 generate_ids_array(
220 "cardano_transaction_snapshot_hashes",
221 BTreeSet::from_iter(
222 self.individual_cardano_transaction_snapshots
223 .keys()
224 .cloned(),
225 ),
226 ),
227 generate_artifact_getter(
228 "cardano_transaction_snapshots",
229 self.individual_cardano_transaction_snapshots,
230 ),
231 generate_list_getter(
232 "cardano_transaction_snapshots_list",
233 self.cardano_transaction_snapshots_list,
234 ),
235 generate_ids_array(
236 "proof_transaction_hashes",
237 BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
238 ),
239 generate_artifact_getter(
240 "cardano_transaction_proofs",
241 self.cardano_transaction_proofs,
242 ),
243 ],
244 true,
245 )
246 }
247
248 fn assemble_code(functions_code: &[String], include_use_btree_map: bool) -> String {
249 format!(
250 "{}{}
251",
252 if include_use_btree_map {
253 "use std::collections::BTreeMap;
254
255"
256 } else {
257 ""
258 },
259 functions_code.join(
260 "
261
262"
263 )
264 )
265 }
266
267 fn read_artifacts_json_file(json_file: &Path) -> BTreeMap<ArtifactId, FileContent> {
268 let file = File::open(json_file).unwrap();
269 let parsed_json: serde_json::Value = serde_json::from_reader(&file).unwrap();
270
271 let json_object = parsed_json.as_object().unwrap();
272 let res: Result<Vec<_>, _> = json_object
273 .iter()
274 .map(|(key, value)| extract_artifact_id_and_content(key, value))
275 .collect();
276
277 BTreeMap::from_iter(res.unwrap())
278 }
279}
280
281pub fn extract_cardano_stake_distribution_epochs(
282 individual_csds: &BTreeMap<ArtifactId, FileContent>,
283) -> Vec<String> {
284 individual_csds
285 .values()
286 .map(|content| {
287 let json_value: serde_json::Value =
288 serde_json::from_str(content).unwrap_or_else(|err| {
289 panic!(
290 "Failed to parse JSON in csd content: {}\nError: {}",
291 content, err
292 );
293 });
294
295 json_value
296 .get("epoch")
297 .and_then(|epoch| epoch.as_u64().map(|s| s.to_string()))
298 .unwrap_or_else(|| {
299 panic!("Epoch not found or invalid in csd content: {}", content);
300 })
301 })
302 .collect()
303}
304
305fn extract_artifact_id_and_content(
306 key: &String,
307 value: &serde_json::Value,
308) -> Result<(ArtifactId, FileContent), String> {
309 let json_content = serde_json::to_string_pretty(value).map_err(|e| e.to_string())?;
310 Ok((key.to_owned(), json_content))
311}
312
313pub fn list_json_files_in_folder(folder: &Path) -> impl Iterator<Item = fs::DirEntry> + '_ {
314 crate::list_files_in_folder(folder)
315 .filter(|e| e.file_name().to_string_lossy().ends_with(".json"))
316}
317
318pub fn generate_artifact_getter(
320 fun_name: &str,
321 source_jsons: BTreeMap<ArtifactId, FileContent>,
322) -> String {
323 let mut artifacts_list = String::new();
324
325 for (artifact_id, file_content) in source_jsons {
326 write!(
327 artifacts_list,
328 r###"
329 (
330 "{}",
331 r#"{}"#
332 ),"###,
333 artifact_id, file_content
334 )
335 .unwrap();
336 }
337
338 format!(
339 r###"pub(crate) fn {}() -> BTreeMap<String, String> {{
340 [{}
341 ]
342 .into_iter()
343 .map(|(k, v)| (k.to_owned(), v.to_owned()))
344 .collect()
345}}"###,
346 fun_name, artifacts_list
347 )
348}
349
350pub fn generate_list_getter(fun_name: &str, source_json: FileContent) -> String {
352 format!(
353 r###"pub(crate) fn {}() -> &'static str {{
354 r#"{}"#
355}}"###,
356 fun_name, source_json
357 )
358}
359
360pub fn generate_ids_array(array_name: &str, ids: BTreeSet<ArtifactId>) -> String {
362 let mut ids_list = String::new();
363
364 for id in &ids {
365 write!(
366 ids_list,
367 r#"
368 "{}","#,
369 id
370 )
371 .unwrap();
372 }
373
374 format!(
375 r###"pub(crate) const fn {}<'a>() -> [&'a str; {}] {{
376 [{}
377 ]
378}}"###,
379 array_name,
380 ids.len(),
381 ids_list,
382 )
383}
384
385#[cfg(test)]
386mod tests {
387 use crate::get_temp_dir;
388
389 use super::*;
390
391 #[test]
392 fn generate_ids_array_with_empty_data() {
393 assert_eq!(
394 generate_ids_array("snapshots_digests", BTreeSet::new()),
395 "pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 0] {
396 [
397 ]
398}"
399 );
400 }
401
402 #[test]
403 fn generate_ids_array_with_non_empty_data() {
404 assert_eq!(
405 generate_ids_array(
406 "snapshots_digests",
407 BTreeSet::from_iter(["abc".to_string(), "def".to_string(), "hij".to_string()])
408 ),
409 r#"pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 3] {
410 [
411 "abc",
412 "def",
413 "hij",
414 ]
415}"#
416 );
417 }
418
419 #[test]
420 fn assemble_code_with_btree_use() {
421 assert_eq!(
422 "use std::collections::BTreeMap;
423
424fn a() {}
425
426fn b() {}
427",
428 FakeAggregatorData::assemble_code(
429 &["fn a() {}".to_string(), "fn b() {}".to_string()],
430 true
431 )
432 )
433 }
434
435 #[test]
436 fn assemble_code_without_btree_use() {
437 assert_eq!(
438 "fn a() {}
439
440fn b() {}
441",
442 FakeAggregatorData::assemble_code(
443 &["fn a() {}".to_string(), "fn b() {}".to_string()],
444 false
445 )
446 )
447 }
448
449 #[test]
450 fn parse_artifacts_json_into_btree_of_key_and_pretty_sub_json() {
451 let dir = get_temp_dir("read_artifacts_json_file");
452 let file = dir.join("test.json");
453 fs::write(
454 &file,
455 r#"{
456 "hash1": { "name": "artifact1" },
457 "hash2": { "name": "artifact2" }
458}"#,
459 )
460 .unwrap();
461
462 let id_per_json = FakeAggregatorData::read_artifacts_json_file(&file);
463
464 let expected = BTreeMap::from([
465 (
466 "hash1".to_string(),
467 r#"{
468 "name": "artifact1"
469}"#
470 .to_string(),
471 ),
472 (
473 "hash2".to_string(),
474 r#"{
475 "name": "artifact2"
476}"#
477 .to_string(),
478 ),
479 ]);
480 assert_eq!(expected, id_per_json);
481 }
482
483 #[test]
484 fn extract_csd_epochs_with_valid_data() {
485 let mut csds = BTreeMap::new();
486 csds.insert(
487 "csd-123".to_string(),
488 r#"{"hash": "csd-123", "epoch": 123}"#.to_string(),
489 );
490 csds.insert(
491 "csd-456".to_string(),
492 r#"{"hash": "csd-456", "epoch": 456}"#.to_string(),
493 );
494
495 let epochs = extract_cardano_stake_distribution_epochs(&csds);
496
497 assert_eq!(epochs, vec![123.to_string(), 456.to_string()]);
498 }
499
500 #[test]
501 #[should_panic(expected = "Failed to parse JSON in csd content")]
502 fn extract_csd_epochs_with_invalid_json() {
503 let mut csds = BTreeMap::new();
504 csds.insert(
505 "csd-123".to_string(),
506 r#""hash": "csd-123", "epoch": "123"#.to_string(),
507 );
508
509 extract_cardano_stake_distribution_epochs(&csds);
510 }
511
512 #[test]
513 #[should_panic(expected = "Epoch not found or invalid in csd content")]
514 fn test_extract_csd_epochs_with_missing_epoch() {
515 let mut csds = BTreeMap::new();
516 csds.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());
517
518 extract_cardano_stake_distribution_epochs(&csds);
519 }
520
521 #[test]
522 fn test_extract_csd_epochs_with_empty_map() {
523 let csds = BTreeMap::new();
524
525 let epochs = extract_cardano_stake_distribution_epochs(&csds);
526
527 assert!(epochs.is_empty());
528 }
529}