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