@@ -110,6 +110,11 @@ impl FakeAggregatorData {
110110 }
111111
112112 pub fn generate_code_for_ids ( self ) -> String {
113+ let cardano_stake_distributions_per_epoch =
114+ extract_item_by_epoch ( & self . individual_cardano_stake_distributions , "/epoch" ) ;
115+ let cardano_database_snapshots_per_epoch =
116+ extract_item_list_per_epoch ( & self . cardano_database_snapshots_list , "/beacon/epoch" ) ;
117+
113118 Self :: assemble_code (
114119 & [
115120 generate_ids_array (
@@ -128,16 +133,18 @@ impl FakeAggregatorData {
128133 self . individual_cardano_stake_distributions . keys ( ) . cloned ( ) ,
129134 ) ,
130135 ) ,
131- generate_ids_array (
136+ generate_epoch_array (
132137 "cardano_stake_distribution_epochs" ,
133- BTreeSet :: from_iter ( extract_cardano_stake_distribution_epochs (
134- & self . individual_cardano_stake_distributions ,
135- ) ) ,
138+ BTreeSet :: from_iter ( cardano_stake_distributions_per_epoch. keys ( ) . cloned ( ) ) ,
136139 ) ,
137140 generate_ids_array (
138141 "cardano_database_snapshot_hashes" ,
139142 BTreeSet :: from_iter ( self . individual_cardano_database_snapshots . keys ( ) . cloned ( ) ) ,
140143 ) ,
144+ generate_epoch_array (
145+ "cardano_database_snapshot_epochs" ,
146+ BTreeSet :: from_iter ( cardano_database_snapshots_per_epoch. keys ( ) . cloned ( ) ) ,
147+ ) ,
141148 generate_ids_array (
142149 "certificate_hashes" ,
143150 BTreeSet :: from_iter ( self . individual_certificates . keys ( ) . cloned ( ) ) ,
@@ -158,6 +165,11 @@ impl FakeAggregatorData {
158165 }
159166
160167 pub fn generate_code_for_all_data ( self ) -> String {
168+ let cardano_stake_distributions_per_epoch =
169+ extract_item_by_epoch ( & self . individual_cardano_stake_distributions , "/epoch" ) ;
170+ let cardano_database_snapshots_per_epoch =
171+ extract_item_list_per_epoch ( & self . cardano_database_snapshots_list , "/beacon/epoch" ) ;
172+
161173 Self :: assemble_code (
162174 & [
163175 generate_list_getter ( "status" , self . status ) ,
@@ -188,11 +200,13 @@ impl FakeAggregatorData {
188200 self . individual_cardano_stake_distributions . keys ( ) . cloned ( ) ,
189201 ) ,
190202 ) ,
191- generate_ids_array (
203+ generate_epoch_array (
192204 "cardano_stake_distribution_epochs" ,
193- BTreeSet :: from_iter ( extract_cardano_stake_distribution_epochs (
194- & self . individual_cardano_stake_distributions ,
195- ) ) ,
205+ BTreeSet :: from_iter ( cardano_stake_distributions_per_epoch. keys ( ) . cloned ( ) ) ,
206+ ) ,
207+ generate_artifact_per_epoch_getter (
208+ "cardano_stake_distributions_per_epoch" ,
209+ extract_item_by_epoch ( & self . individual_cardano_stake_distributions , "/epoch" ) ,
196210 ) ,
197211 generate_artifact_getter (
198212 "cardano_stake_distributions" ,
@@ -210,6 +224,10 @@ impl FakeAggregatorData {
210224 "cardano_database_snapshot_hashes" ,
211225 BTreeSet :: from_iter ( self . individual_cardano_database_snapshots . keys ( ) . cloned ( ) ) ,
212226 ) ,
227+ generate_epoch_array (
228+ "cardano_database_snapshot_epochs" ,
229+ BTreeSet :: from_iter ( cardano_database_snapshots_per_epoch. keys ( ) . cloned ( ) ) ,
230+ ) ,
213231 generate_artifact_getter (
214232 "cardano_database_snapshots" ,
215233 self . individual_cardano_database_snapshots ,
@@ -218,6 +236,10 @@ impl FakeAggregatorData {
218236 "cardano_database_snapshot_list" ,
219237 self . cardano_database_snapshots_list ,
220238 ) ,
239+ generate_artifact_per_epoch_getter (
240+ "cardano_database_snapshot_list_per_epoch" ,
241+ cardano_database_snapshots_per_epoch,
242+ ) ,
221243 generate_artifact_getter ( "certificates" , self . individual_certificates ) ,
222244 generate_list_getter ( "certificate_list" , self . certificates_list ) ,
223245 generate_ids_array (
@@ -280,27 +302,6 @@ impl FakeAggregatorData {
280302 }
281303}
282304
283- pub fn extract_cardano_stake_distribution_epochs (
284- individual_csds : & BTreeMap < ArtifactId , FileContent > ,
285- ) -> Vec < String > {
286- individual_csds
287- . values ( )
288- . map ( |content| {
289- let json_value: serde_json:: Value =
290- serde_json:: from_str ( content) . unwrap_or_else ( |err| {
291- panic ! ( "Failed to parse JSON in csd content: {content}\n Error: {err}" ) ;
292- } ) ;
293-
294- json_value
295- . get ( "epoch" )
296- . and_then ( |epoch| epoch. as_u64 ( ) . map ( |s| s. to_string ( ) ) )
297- . unwrap_or_else ( || {
298- panic ! ( "Epoch not found or invalid in csd content: {content}" ) ;
299- } )
300- } )
301- . collect ( )
302- }
303-
304305fn extract_artifact_id_and_content (
305306 key : & String ,
306307 value : & serde_json:: Value ,
@@ -309,6 +310,57 @@ fn extract_artifact_id_and_content(
309310 Ok ( ( key. to_owned ( ) , json_content) )
310311}
311312
313+ /// Takes a map of json string indexed by hashes and re-indexes them using their epoch
314+ ///
315+ /// Each item in the map must contain an epoch value at the specified JSON pointer location.
316+ pub fn extract_item_by_epoch (
317+ items_per_hash : & BTreeMap < String , String > ,
318+ json_pointer_for_epoch : & str ,
319+ ) -> BTreeMap < u64 , String > {
320+ let mut res = BTreeMap :: new ( ) ;
321+
322+ for ( key, value) in items_per_hash {
323+ let parsed_json: serde_json:: Value = serde_json:: from_str ( value)
324+ . unwrap_or_else ( |_| panic ! ( "Could not parse JSON entity '{key}'" ) ) ;
325+ let epoch = parsed_json
326+ . pointer ( json_pointer_for_epoch)
327+ . unwrap_or_else ( || panic ! ( "missing `{json_pointer_for_epoch}` for JSON entity '{key}'" ) )
328+ . as_u64 ( )
329+ . unwrap_or_else ( || {
330+ panic ! ( "`{json_pointer_for_epoch}` is not a number for JSON entity '{key}'" )
331+ } ) ;
332+ res. insert ( epoch, value. clone ( ) ) ;
333+ }
334+
335+ res
336+ }
337+
338+ /// Takes a JSON string containing a list of items and extracts them into a map keyed by epoch.
339+ ///
340+ /// Each item in the list must contain an epoch value at the specified JSON pointer location.
341+ pub fn extract_item_list_per_epoch (
342+ source : & str ,
343+ json_pointer_for_epoch : & str ,
344+ ) -> BTreeMap < u64 , String > {
345+ let parsed_json: Vec < serde_json:: Value > =
346+ serde_json:: from_str ( source) . expect ( "Failed to parse JSON list" ) ;
347+ let mut list_per_epoch = BTreeMap :: < u64 , Vec < serde_json:: Value > > :: new ( ) ;
348+
349+ for item in parsed_json {
350+ let epoch = item
351+ . pointer ( json_pointer_for_epoch)
352+ . unwrap_or_else ( || panic ! ( "missing `{json_pointer_for_epoch}` for a json value" ) )
353+ . as_u64 ( )
354+ . unwrap_or_else ( || panic ! ( "`{json_pointer_for_epoch}` is not a number" ) ) ;
355+ list_per_epoch. entry ( epoch) . or_default ( ) . push ( item) ;
356+ }
357+
358+ list_per_epoch
359+ . into_iter ( )
360+ . map ( |( k, v) | ( k, serde_json:: to_string ( & v) . unwrap ( ) ) )
361+ . collect ( )
362+ }
363+
312364pub fn list_json_files_in_folder ( folder : & Path ) -> impl Iterator < Item = fs:: DirEntry > + ' _ {
313365 crate :: list_files_in_folder ( folder)
314366 . filter ( |e| e. file_name ( ) . to_string_lossy ( ) . ends_with ( ".json" ) )
@@ -344,6 +396,36 @@ pub fn generate_artifact_getter(
344396 )
345397}
346398
399+ // pub(crate) fn $fun_name()() -> BTreeMap<u64, String>
400+ pub fn generate_artifact_per_epoch_getter (
401+ fun_name : & str ,
402+ source_jsons : BTreeMap < u64 , FileContent > ,
403+ ) -> String {
404+ let mut artifacts_list = String :: new ( ) ;
405+
406+ for ( artifact_id, file_content) in source_jsons {
407+ write ! (
408+ artifacts_list,
409+ r###"
410+ (
411+ {artifact_id},
412+ r#"{file_content}"#
413+ ),"###
414+ )
415+ . unwrap ( ) ;
416+ }
417+
418+ format ! (
419+ r###"pub(crate) fn {fun_name}() -> BTreeMap<u64, String> {{
420+ [{artifacts_list}
421+ ]
422+ .into_iter()
423+ .map(|(k, v)| (k.to_owned(), v.to_owned()))
424+ .collect()
425+ }}"###
426+ )
427+ }
428+
347429/// pub(crate) fn $fun_name() -> &'static str
348430pub fn generate_list_getter ( fun_name : & str , source_json : FileContent ) -> String {
349431 format ! (
@@ -377,6 +459,30 @@ pub fn generate_ids_array(array_name: &str, ids: BTreeSet<ArtifactId>) -> String
377459 )
378460}
379461
462+ /// pub(crate) fn $array_name() -> [u64; $epoch.len]
463+ pub fn generate_epoch_array ( array_name : & str , epoch : BTreeSet < u64 > ) -> String {
464+ let mut ids_list = String :: new ( ) ;
465+
466+ for id in & epoch {
467+ write ! (
468+ ids_list,
469+ r#"
470+ {id},"#
471+ )
472+ . unwrap ( ) ;
473+ }
474+
475+ format ! (
476+ r###"pub(crate) const fn {}() -> [u64; {}] {{
477+ [{}
478+ ]
479+ }}"### ,
480+ array_name,
481+ epoch. len( ) ,
482+ ids_list,
483+ )
484+ }
485+
380486#[ cfg( test) ]
381487mod tests {
382488 use crate :: get_temp_dir;
@@ -476,48 +582,105 @@ fn b() {}
476582 }
477583
478584 #[ test]
479- fn extract_csd_epochs_with_valid_data ( ) {
480- let mut csds = BTreeMap :: new ( ) ;
481- csds. insert (
482- "csd-123" . to_string ( ) ,
483- r#"{"hash": "csd-123", "epoch": 123}"# . to_string ( ) ,
484- ) ;
485- csds. insert (
486- "csd-456" . to_string ( ) ,
487- r#"{"hash": "csd-456", "epoch": 456}"# . to_string ( ) ,
488- ) ;
489-
490- let epochs = extract_cardano_stake_distribution_epochs ( & csds) ;
585+ fn test_extract_item_by_epoch_by_epoch_with_valid_data ( ) {
586+ let items_per_hash = BTreeMap :: from ( [
587+ (
588+ "hash1" . to_string ( ) ,
589+ r#"{"bar":4,"epoch":3,"foo":"...","hash":"2"}"# . to_string ( ) ,
590+ ) ,
591+ (
592+ "hash2" . to_string ( ) ,
593+ r#"{"bar":7,"epoch":2,"foo":"...","hash":"1"}"# . to_string ( ) ,
594+ ) ,
595+ ] ) ;
491596
492- assert_eq ! ( epochs, vec![ 123 . to_string( ) , 456 . to_string( ) ] ) ;
597+ // note: values are not re-serialized, so they are kept as is
598+ let item_per_epoch = extract_item_by_epoch ( & items_per_hash, "/epoch" ) ;
599+ assert_eq ! (
600+ BTreeMap :: from( [
601+ ( 3 , items_per_hash. get( "hash1" ) . unwrap( ) . to_string( ) ) ,
602+ ( 2 , items_per_hash. get( "hash2" ) . unwrap( ) . to_string( ) )
603+ ] ) ,
604+ item_per_epoch
605+ )
493606 }
494607
495608 #[ test]
496- #[ should_panic( expected = "Failed to parse JSON in csd content " ) ]
497- fn extract_csd_epochs_with_invalid_json ( ) {
498- let mut csds = BTreeMap :: new ( ) ;
499- csds . insert (
609+ #[ should_panic( expected = "Could not parse JSON entity ' csd-123' " ) ]
610+ fn test_extract_item_by_epoch_by_epoch_with_invalid_json ( ) {
611+ let mut items_per_hash = BTreeMap :: new ( ) ;
612+ items_per_hash . insert (
500613 "csd-123" . to_string ( ) ,
501614 r#""hash": "csd-123", "epoch": "123"# . to_string ( ) ,
502615 ) ;
503616
504- extract_cardano_stake_distribution_epochs ( & csds) ;
617+ extract_item_by_epoch ( & items_per_hash, "/epoch" ) ;
618+ }
619+
620+ #[ test]
621+ #[ should_panic( expected = "missing `/epoch` for JSON entity 'csd-123'" ) ]
622+ fn test_extract_item_by_epoch_with_missing_epoch ( ) {
623+ let mut items_per_hash = BTreeMap :: new ( ) ;
624+ items_per_hash. insert ( "csd-123" . to_string ( ) , r#"{"hash": "csd-123"}"# . to_string ( ) ) ;
625+
626+ extract_item_by_epoch ( & items_per_hash, "/epoch" ) ;
627+ }
628+
629+ #[ test]
630+ fn test_extract_item_by_epoch_with_empty_map ( ) {
631+ let items_per_hash = BTreeMap :: new ( ) ;
632+
633+ let epochs = extract_item_by_epoch ( & items_per_hash, "/epoch" ) ;
634+
635+ assert ! ( epochs. is_empty( ) ) ;
636+ }
637+
638+ #[ test]
639+ fn test_extract_item_list_per_epoch_for_epoch ( ) {
640+ let list_per_epoch_json = r#"[
641+ { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." },
642+ { "beacon": { "epoch": 2}, "hash":"2","foo":"..." },
643+ { "beacon": { "epoch": 1}, "hash":"1","foo":"..." }
644+ ]"# ;
645+
646+ // note: values are re-serialized, so serde_json reorders the keys
647+ let map_per_epoch = extract_item_list_per_epoch ( list_per_epoch_json, "/beacon/epoch" ) ;
648+ assert_eq ! (
649+ BTreeMap :: from( [
650+ (
651+ 1 ,
652+ r#"[{"beacon":{"bar":4,"epoch":1},"foo":"...","hash":"3"},{"beacon":{"epoch":1},"foo":"...","hash":"1"}]"#
653+ . to_string( )
654+ ) ,
655+ ( 2 , r#"[{"beacon":{"epoch":2},"foo":"...","hash":"2"}]"# . to_string( ) ) ,
656+ ] ) ,
657+ map_per_epoch
658+ )
659+ }
660+
661+ #[ test]
662+ #[ should_panic( expected = "Failed to parse JSON list" ) ]
663+ fn test_extract_item_list_per_epoch_with_invalid_json ( ) {
664+ // invalid because of the trailing comma
665+ let list_per_epoch_json =
666+ r#"[ { "beacon": { "epoch": 1, "bar": 4 }, "hash":"3","foo":"..." }, ]"# ;
667+
668+ extract_item_list_per_epoch ( list_per_epoch_json, "/epoch" ) ;
505669 }
506670
507671 #[ test]
508- #[ should_panic( expected = "Epoch not found or invalid in csd content" ) ]
509- fn test_extract_csd_epochs_with_missing_epoch ( ) {
510- let mut csds = BTreeMap :: new ( ) ;
511- csds. insert ( "csd-123" . to_string ( ) , r#"{"hash": "csd-123"}"# . to_string ( ) ) ;
672+ #[ should_panic( expected = "missing `/epoch` for a json value" ) ]
673+ fn test_extract_item_list_per_epoch_with_missing_epoch ( ) {
674+ let list_per_epoch_json = r#"[ { "beacon": { "bar": 4 }, "hash":"3","foo":"..." } ]"# ;
512675
513- extract_cardano_stake_distribution_epochs ( & csds ) ;
676+ extract_item_list_per_epoch ( list_per_epoch_json , "/epoch" ) ;
514677 }
515678
516679 #[ test]
517- fn test_extract_csd_epochs_with_empty_map ( ) {
518- let csds = BTreeMap :: new ( ) ;
680+ fn test_extract_item_list_per_epoch_with_list ( ) {
681+ let list_per_epoch_json = "[]" ;
519682
520- let epochs = extract_cardano_stake_distribution_epochs ( & csds ) ;
683+ let epochs = extract_item_list_per_epoch ( list_per_epoch_json , "/epoch" ) ;
521684
522685 assert ! ( epochs. is_empty( ) ) ;
523686 }
0 commit comments