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 status: FileContent,
18
19 epoch_settings: FileContent,
20
21 certificates_list: FileContent,
22 individual_certificates: BTreeMap<ArtifactId, FileContent>,
23
24 snapshots_list: FileContent,
25 individual_snapshots: BTreeMap<ArtifactId, FileContent>,
26
27 mithril_stake_distributions_list: FileContent,
28 individual_mithril_stake_distributions: BTreeMap<ArtifactId, FileContent>,
29
30 cardano_transaction_snapshots_list: FileContent,
31 individual_cardano_transaction_snapshots: BTreeMap<ArtifactId, FileContent>,
32 cardano_transaction_proofs: BTreeMap<ArtifactId, FileContent>,
33
34 cardano_blocks_transactions_snapshots_list: FileContent,
35 individual_cardano_blocks_transactions_snapshots: BTreeMap<ArtifactId, FileContent>,
36 cardano_block_proofs: BTreeMap<ArtifactId, FileContent>,
37 cardano_transaction_v2_proofs: BTreeMap<ArtifactId, FileContent>,
38
39 cardano_stake_distributions_list: FileContent,
40 individual_cardano_stake_distributions: BTreeMap<ArtifactId, FileContent>,
41
42 cardano_database_snapshots_list: FileContent,
43 individual_cardano_database_snapshots: BTreeMap<ArtifactId, FileContent>,
44}
45
46impl FakeAggregatorData {
47 pub fn load_from_folder(folder: &Path) -> Self {
48 let mut data = FakeAggregatorData::default();
49
50 for entry in list_json_files_in_folder(folder) {
51 let filename = entry.file_name().to_string_lossy().to_string();
52 let file_content = fs::read_to_string(entry.path()).unwrap_or_else(|_| {
53 panic!(
54 "Could not read file content, file_path: {}",
55 entry.path().display()
56 )
57 });
58
59 match filename.as_str() {
60 "status.json" => {
61 data.status = file_content;
62 }
63 "epoch-settings.json" => {
64 data.epoch_settings = file_content;
65 }
66 "mithril-stake-distributions-list.json" => {
67 data.mithril_stake_distributions_list = file_content;
68 }
69 "snapshots-list.json" => {
70 data.snapshots_list = file_content;
71 }
72 "cardano-stake-distributions-list.json" => {
73 data.cardano_stake_distributions_list = file_content;
74 }
75 "cardano-databases-list.json" => {
76 data.cardano_database_snapshots_list = file_content;
77 }
78 "certificates-list.json" => {
79 data.certificates_list = file_content;
80 }
81 "ctx-snapshots-list.json" => {
82 data.cardano_transaction_snapshots_list = file_content;
83 }
84 "cardano-blocks-txs-snapshots-list.json" => {
85 data.cardano_blocks_transactions_snapshots_list = file_content;
86 }
87 "mithril-stake-distributions.json" => {
88 data.individual_mithril_stake_distributions =
89 Self::read_artifacts_json_file(&entry.path());
90 }
91 "snapshots.json" => {
92 data.individual_snapshots = Self::read_artifacts_json_file(&entry.path());
93 }
94 "cardano-stake-distributions.json" => {
95 data.individual_cardano_stake_distributions =
96 Self::read_artifacts_json_file(&entry.path());
97 }
98 "cardano-databases.json" => {
99 data.individual_cardano_database_snapshots =
100 Self::read_artifacts_json_file(&entry.path());
101 }
102 "certificates.json" => {
103 data.individual_certificates = Self::read_artifacts_json_file(&entry.path());
104 }
105 "ctx-snapshots.json" => {
106 data.individual_cardano_transaction_snapshots =
107 Self::read_artifacts_json_file(&entry.path());
108 }
109 "cardano-blocks-txs-snapshots.json" => {
110 data.individual_cardano_blocks_transactions_snapshots =
111 Self::read_artifacts_json_file(&entry.path());
112 }
113 "ctx-proofs.json" => {
114 data.cardano_transaction_proofs = Self::read_artifacts_json_file(&entry.path());
115 }
116 "ctx-proofs-v2.json" => {
117 data.cardano_transaction_v2_proofs =
118 Self::read_artifacts_json_file(&entry.path());
119 }
120 "cblk-proofs.json" => {
121 data.cardano_block_proofs = Self::read_artifacts_json_file(&entry.path());
122 }
123 _ => {}
125 }
126 }
127
128 data
129 }
130
131 pub fn generate_code_for_ids(self) -> String {
132 let cardano_stake_distributions_per_epoch =
133 extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch");
134 let cardano_database_snapshots_per_epoch =
135 extract_item_list_per_epoch(&self.cardano_database_snapshots_list, "/beacon/epoch");
136
137 Self::assemble_code(
138 &[
139 generate_ids_array(
140 "snapshot_digests",
141 BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
142 ),
143 generate_ids_array(
144 "mithril_stake_distribution_hashes",
145 BTreeSet::from_iter(
146 self.individual_mithril_stake_distributions.keys().cloned(),
147 ),
148 ),
149 generate_ids_array(
150 "cardano_stake_distribution_hashes",
151 BTreeSet::from_iter(
152 self.individual_cardano_stake_distributions.keys().cloned(),
153 ),
154 ),
155 generate_epoch_array(
156 "cardano_stake_distribution_epochs",
157 BTreeSet::from_iter(cardano_stake_distributions_per_epoch.keys().cloned()),
158 ),
159 generate_ids_array(
160 "cardano_database_snapshot_hashes",
161 BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
162 ),
163 generate_epoch_array(
164 "cardano_database_snapshot_epochs",
165 BTreeSet::from_iter(cardano_database_snapshots_per_epoch.keys().cloned()),
166 ),
167 generate_ids_array(
168 "certificate_hashes",
169 BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
170 ),
171 generate_ids_array(
172 "cardano_transaction_snapshot_hashes",
173 BTreeSet::from_iter(
174 self.individual_cardano_transaction_snapshots.keys().cloned(),
175 ),
176 ),
177 generate_ids_array(
178 "cardano_blocks_transactions_snapshot_hashes",
179 BTreeSet::from_iter(
180 self.individual_cardano_blocks_transactions_snapshots.keys().cloned(),
181 ),
182 ),
183 generate_ids_array(
184 "proof_transaction_hashes",
185 BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
186 ),
187 generate_ids_array(
188 "proof_v2_transaction_hashes",
189 BTreeSet::from_iter(self.cardano_transaction_v2_proofs.keys().cloned()),
190 ),
191 generate_ids_array(
192 "proof_v2_block_hashes",
193 BTreeSet::from_iter(self.cardano_block_proofs.keys().cloned()),
194 ),
195 ],
196 false,
197 )
198 }
199
200 pub fn generate_code_for_all_data(self) -> String {
201 let cardano_stake_distributions_per_epoch =
202 extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch");
203 let cardano_database_snapshots_per_epoch =
204 extract_item_list_per_epoch(&self.cardano_database_snapshots_list, "/beacon/epoch");
205
206 Self::assemble_code(
207 &[
208 generate_list_getter("status", self.status),
209 generate_list_getter("epoch_settings", self.epoch_settings),
210 generate_ids_array(
211 "snapshot_digests",
212 BTreeSet::from_iter(self.individual_snapshots.keys().cloned()),
213 ),
214 generate_artifact_getter("snapshots", self.individual_snapshots),
215 generate_list_getter("snapshot_list", self.snapshots_list),
216 generate_ids_array(
217 "mithril_stake_distribution_hashes",
218 BTreeSet::from_iter(
219 self.individual_mithril_stake_distributions.keys().cloned(),
220 ),
221 ),
222 generate_artifact_getter(
223 "mithril_stake_distributions",
224 self.individual_mithril_stake_distributions,
225 ),
226 generate_list_getter(
227 "mithril_stake_distribution_list",
228 self.mithril_stake_distributions_list,
229 ),
230 generate_ids_array(
231 "cardano_stake_distribution_hashes",
232 BTreeSet::from_iter(
233 self.individual_cardano_stake_distributions.keys().cloned(),
234 ),
235 ),
236 generate_epoch_array(
237 "cardano_stake_distribution_epochs",
238 BTreeSet::from_iter(cardano_stake_distributions_per_epoch.keys().cloned()),
239 ),
240 generate_artifact_per_epoch_getter(
241 "cardano_stake_distributions_per_epoch",
242 extract_item_by_epoch(&self.individual_cardano_stake_distributions, "/epoch"),
243 ),
244 generate_artifact_getter(
245 "cardano_stake_distributions",
246 self.individual_cardano_stake_distributions,
247 ),
248 generate_list_getter(
249 "cardano_stake_distribution_list",
250 self.cardano_stake_distributions_list,
251 ),
252 generate_ids_array(
253 "certificate_hashes",
254 BTreeSet::from_iter(self.individual_certificates.keys().cloned()),
255 ),
256 generate_ids_array(
257 "cardano_database_snapshot_hashes",
258 BTreeSet::from_iter(self.individual_cardano_database_snapshots.keys().cloned()),
259 ),
260 generate_epoch_array(
261 "cardano_database_snapshot_epochs",
262 BTreeSet::from_iter(cardano_database_snapshots_per_epoch.keys().cloned()),
263 ),
264 generate_artifact_getter(
265 "cardano_database_snapshots",
266 self.individual_cardano_database_snapshots,
267 ),
268 generate_list_getter(
269 "cardano_database_snapshot_list",
270 self.cardano_database_snapshots_list,
271 ),
272 generate_artifact_per_epoch_getter(
273 "cardano_database_snapshot_list_per_epoch",
274 cardano_database_snapshots_per_epoch,
275 ),
276 generate_artifact_getter("certificates", self.individual_certificates),
277 generate_list_getter("certificate_list", self.certificates_list),
278 generate_ids_array(
279 "cardano_transaction_snapshot_hashes",
280 BTreeSet::from_iter(
281 self.individual_cardano_transaction_snapshots.keys().cloned(),
282 ),
283 ),
284 generate_artifact_getter(
285 "cardano_transaction_snapshots",
286 self.individual_cardano_transaction_snapshots,
287 ),
288 generate_list_getter(
289 "cardano_transaction_snapshots_list",
290 self.cardano_transaction_snapshots_list,
291 ),
292 generate_ids_array(
293 "proof_transaction_hashes",
294 BTreeSet::from_iter(self.cardano_transaction_proofs.keys().cloned()),
295 ),
296 generate_artifact_getter(
297 "cardano_transaction_proofs",
298 self.cardano_transaction_proofs,
299 ),
300 generate_ids_array(
301 "cardano_blocks_transactions_snapshot_hashes",
302 BTreeSet::from_iter(
303 self.individual_cardano_blocks_transactions_snapshots.keys().cloned(),
304 ),
305 ),
306 generate_artifact_getter(
307 "cardano_blocks_transactions_snapshots",
308 self.individual_cardano_blocks_transactions_snapshots,
309 ),
310 generate_list_getter(
311 "cardano_blocks_transactions_snapshots_list",
312 self.cardano_blocks_transactions_snapshots_list,
313 ),
314 generate_ids_array(
315 "proof_v2_block_hashes",
316 BTreeSet::from_iter(self.cardano_block_proofs.keys().cloned()),
317 ),
318 generate_artifact_getter("cardano_block_proofs", self.cardano_block_proofs),
319 generate_ids_array(
320 "proof_v2_transaction_hashes",
321 BTreeSet::from_iter(self.cardano_transaction_v2_proofs.keys().cloned()),
322 ),
323 generate_artifact_getter(
324 "cardano_transaction_proofs_v2",
325 self.cardano_transaction_v2_proofs,
326 ),
327 ],
328 true,
329 )
330 }
331
332 fn assemble_code(functions_code: &[String], include_use_btree_map: bool) -> String {
333 format!(
334 "{}{}
335",
336 if include_use_btree_map {
337 "use std::collections::BTreeMap;
338
339"
340 } else {
341 ""
342 },
343 functions_code.join(
344 "
345
346"
347 )
348 )
349 }
350
351 fn read_artifacts_json_file(json_file: &Path) -> BTreeMap<ArtifactId, FileContent> {
352 let file = File::open(json_file).unwrap();
353 let parsed_json: serde_json::Value = serde_json::from_reader(&file).unwrap();
354
355 let json_object = parsed_json.as_object().unwrap();
356 let res: Result<Vec<_>, _> = json_object
357 .iter()
358 .map(|(key, value)| extract_artifact_id_and_content(key, value))
359 .collect();
360
361 BTreeMap::from_iter(res.unwrap())
362 }
363}
364
365fn extract_artifact_id_and_content(
366 key: &String,
367 value: &serde_json::Value,
368) -> Result<(ArtifactId, FileContent), String> {
369 let json_content = serde_json::to_string_pretty(value).map_err(|e| e.to_string())?;
370 Ok((key.to_owned(), json_content))
371}
372
373pub fn extract_item_by_epoch(
377 items_per_hash: &BTreeMap<String, String>,
378 json_pointer_for_epoch: &str,
379) -> BTreeMap<u64, String> {
380 let mut res = BTreeMap::new();
381
382 for (key, value) in items_per_hash {
383 let parsed_json: serde_json::Value = serde_json::from_str(value)
384 .unwrap_or_else(|_| panic!("Could not parse JSON entity '{key}'"));
385 let epoch = parsed_json
386 .pointer(json_pointer_for_epoch)
387 .unwrap_or_else(|| panic!("missing `{json_pointer_for_epoch}` for JSON entity '{key}'"))
388 .as_u64()
389 .unwrap_or_else(|| {
390 panic!("`{json_pointer_for_epoch}` is not a number for JSON entity '{key}'")
391 });
392 res.insert(epoch, value.clone());
393 }
394
395 res
396}
397
398pub fn extract_item_list_per_epoch(
402 source: &str,
403 json_pointer_for_epoch: &str,
404) -> BTreeMap<u64, String> {
405 let parsed_json: Vec<serde_json::Value> =
406 serde_json::from_str(source).expect("Failed to parse JSON list");
407 let mut list_per_epoch = BTreeMap::<u64, Vec<serde_json::Value>>::new();
408
409 for item in parsed_json {
410 let epoch = item
411 .pointer(json_pointer_for_epoch)
412 .unwrap_or_else(|| panic!("missing `{json_pointer_for_epoch}` for a json value"))
413 .as_u64()
414 .unwrap_or_else(|| panic!("`{json_pointer_for_epoch}` is not a number"));
415 list_per_epoch.entry(epoch).or_default().push(item);
416 }
417
418 list_per_epoch
419 .into_iter()
420 .map(|(k, v)| (k, serde_json::to_string(&v).unwrap()))
421 .collect()
422}
423
424pub fn list_json_files_in_folder(folder: &Path) -> impl Iterator<Item = fs::DirEntry> + '_ {
425 crate::list_files_in_folder(folder)
426 .filter(|e| e.file_name().to_string_lossy().ends_with(".json"))
427}
428
429pub fn generate_artifact_getter(
431 fun_name: &str,
432 source_jsons: BTreeMap<ArtifactId, FileContent>,
433) -> String {
434 let mut artifacts_list = String::new();
435
436 for (artifact_id, file_content) in source_jsons {
437 write!(
438 artifacts_list,
439 r###"
440 (
441 "{artifact_id}",
442 r#"{file_content}"#
443 ),"###
444 )
445 .unwrap();
446 }
447
448 format!(
449 r###"pub(crate) fn {fun_name}() -> BTreeMap<String, String> {{
450 [{artifacts_list}
451 ]
452 .into_iter()
453 .map(|(k, v)| (k.to_owned(), v.to_owned()))
454 .collect()
455}}"###
456 )
457}
458
459pub fn generate_artifact_per_epoch_getter(
461 fun_name: &str,
462 source_jsons: BTreeMap<u64, FileContent>,
463) -> String {
464 let mut artifacts_list = String::new();
465
466 for (artifact_id, file_content) in source_jsons {
467 write!(
468 artifacts_list,
469 r###"
470 (
471 {artifact_id},
472 r#"{file_content}"#
473 ),"###
474 )
475 .unwrap();
476 }
477
478 format!(
479 r###"pub(crate) fn {fun_name}() -> BTreeMap<u64, String> {{
480 [{artifacts_list}
481 ]
482 .into_iter()
483 .map(|(k, v)| (k.to_owned(), v.to_owned()))
484 .collect()
485}}"###
486 )
487}
488
489pub fn generate_list_getter(fun_name: &str, source_json: FileContent) -> String {
491 format!(
492 r###"pub(crate) fn {fun_name}() -> &'static str {{
493 r#"{source_json}"#
494}}"###
495 )
496}
497
498pub fn generate_ids_array(array_name: &str, ids: BTreeSet<ArtifactId>) -> String {
500 let mut ids_list = String::new();
501
502 for id in &ids {
503 write!(
504 ids_list,
505 r#"
506 "{id}","#
507 )
508 .unwrap();
509 }
510
511 format!(
512 r###"pub(crate) const fn {}<'a>() -> [&'a str; {}] {{
513 [{}
514 ]
515}}"###,
516 array_name,
517 ids.len(),
518 ids_list,
519 )
520}
521
522pub fn generate_epoch_array(array_name: &str, epoch: BTreeSet<u64>) -> String {
524 let mut ids_list = String::new();
525
526 for id in &epoch {
527 write!(
528 ids_list,
529 r#"
530 {id},"#
531 )
532 .unwrap();
533 }
534
535 format!(
536 r###"pub(crate) const fn {}() -> [u64; {}] {{
537 [{}
538 ]
539}}"###,
540 array_name,
541 epoch.len(),
542 ids_list,
543 )
544}
545
546#[cfg(test)]
547mod tests {
548 use crate::get_temp_dir;
549
550 use super::*;
551
552 #[test]
553 fn generate_ids_array_with_empty_data() {
554 assert_eq!(
555 generate_ids_array("snapshots_digests", BTreeSet::new()),
556 "pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 0] {
557 [
558 ]
559}"
560 );
561 }
562
563 #[test]
564 fn generate_ids_array_with_non_empty_data() {
565 assert_eq!(
566 generate_ids_array(
567 "snapshots_digests",
568 BTreeSet::from_iter(["abc".to_string(), "def".to_string(), "hij".to_string()])
569 ),
570 r#"pub(crate) const fn snapshots_digests<'a>() -> [&'a str; 3] {
571 [
572 "abc",
573 "def",
574 "hij",
575 ]
576}"#
577 );
578 }
579
580 #[test]
581 fn assemble_code_with_btree_use() {
582 assert_eq!(
583 "use std::collections::BTreeMap;
584
585fn a() {}
586
587fn b() {}
588",
589 FakeAggregatorData::assemble_code(
590 &["fn a() {}".to_string(), "fn b() {}".to_string()],
591 true
592 )
593 )
594 }
595
596 #[test]
597 fn assemble_code_without_btree_use() {
598 assert_eq!(
599 "fn a() {}
600
601fn b() {}
602",
603 FakeAggregatorData::assemble_code(
604 &["fn a() {}".to_string(), "fn b() {}".to_string()],
605 false
606 )
607 )
608 }
609
610 #[test]
611 fn parse_artifacts_json_into_btree_of_key_and_pretty_sub_json() {
612 let dir = get_temp_dir("read_artifacts_json_file");
613 let file = dir.join("test.json");
614 fs::write(
615 &file,
616 r#"{
617 "hash1": { "name": "artifact1" },
618 "hash2": { "name": "artifact2" }
619}"#,
620 )
621 .unwrap();
622
623 let id_per_json = FakeAggregatorData::read_artifacts_json_file(&file);
624
625 let expected = BTreeMap::from([
626 (
627 "hash1".to_string(),
628 r#"{
629 "name": "artifact1"
630}"#
631 .to_string(),
632 ),
633 (
634 "hash2".to_string(),
635 r#"{
636 "name": "artifact2"
637}"#
638 .to_string(),
639 ),
640 ]);
641 assert_eq!(expected, id_per_json);
642 }
643
644 #[test]
645 fn test_extract_item_by_epoch_by_epoch_with_valid_data() {
646 let items_per_hash = BTreeMap::from([
647 (
648 "hash1".to_string(),
649 r#"{"bar":4,"epoch":3,"foo":"...","hash":"2"}"#.to_string(),
650 ),
651 (
652 "hash2".to_string(),
653 r#"{"bar":7,"epoch":2,"foo":"...","hash":"1"}"#.to_string(),
654 ),
655 ]);
656
657 let item_per_epoch = extract_item_by_epoch(&items_per_hash, "/epoch");
659 assert_eq!(
660 BTreeMap::from([
661 (3, items_per_hash.get("hash1").unwrap().to_string()),
662 (2, items_per_hash.get("hash2").unwrap().to_string())
663 ]),
664 item_per_epoch
665 )
666 }
667
668 #[test]
669 #[should_panic(expected = "Could not parse JSON entity 'csd-123'")]
670 fn test_extract_item_by_epoch_by_epoch_with_invalid_json() {
671 let mut items_per_hash = BTreeMap::new();
672 items_per_hash.insert(
673 "csd-123".to_string(),
674 r#""hash": "csd-123", "epoch": "123"#.to_string(),
675 );
676
677 extract_item_by_epoch(&items_per_hash, "/epoch");
678 }
679
680 #[test]
681 #[should_panic(expected = "missing `/epoch` for JSON entity 'csd-123'")]
682 fn test_extract_item_by_epoch_with_missing_epoch() {
683 let mut items_per_hash = BTreeMap::new();
684 items_per_hash.insert("csd-123".to_string(), r#"{"hash": "csd-123"}"#.to_string());
685
686 extract_item_by_epoch(&items_per_hash, "/epoch");
687 }
688
689 #[test]
690 fn test_extract_item_by_epoch_with_empty_map() {
691 let items_per_hash = BTreeMap::new();
692
693 let epochs = extract_item_by_epoch(&items_per_hash, "/epoch");
694
695 assert!(epochs.is_empty());
696 }
697
698 #[test]
699 fn test_extract_item_list_per_epoch_for_epoch() {
700 let list_per_epoch_json = r#"[
701 { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." },
702 { "beacon": { "epoch": 2}, "hash":"2","foo":"..." },
703 { "beacon": { "epoch": 1}, "hash":"1","foo":"..." }
704 ]"#;
705
706 let map_per_epoch = extract_item_list_per_epoch(list_per_epoch_json, "/beacon/epoch");
708 assert_eq!(
709 BTreeMap::from([
710 (
711 1,
712 r#"[{"beacon":{"bar":4,"epoch":1},"foo":"...","hash":"3"},{"beacon":{"epoch":1},"foo":"...","hash":"1"}]"#
713 .to_string()
714 ),
715 (2, r#"[{"beacon":{"epoch":2},"foo":"...","hash":"2"}]"#.to_string()),
716 ]),
717 map_per_epoch
718 )
719 }
720
721 #[test]
722 #[should_panic(expected = "Failed to parse JSON list")]
723 fn test_extract_item_list_per_epoch_with_invalid_json() {
724 let list_per_epoch_json =
726 r#"[ { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." }, ]"#;
727
728 extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
729 }
730
731 #[test]
732 #[should_panic(expected = "missing `/epoch` for a json value")]
733 fn test_extract_item_list_per_epoch_with_missing_epoch() {
734 let list_per_epoch_json = r#"[ { "beacon": { "bar": 4 }, "hash":"3","foo":"..." } ]"#;
735
736 extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
737 }
738
739 #[test]
740 fn test_extract_item_list_per_epoch_with_list() {
741 let list_per_epoch_json = "[]";
742
743 let epochs = extract_item_list_per_epoch(list_per_epoch_json, "/epoch");
744
745 assert!(epochs.is_empty());
746 }
747}