1#[derive(Debug)]
9pub struct Snapshot {
10 pub ranking: serde_json::Value,
11 pub variances: Option<serde_json::Value>,
12}
13
14fn find_prior_commit_hash(indices: &[serde_json::Value], epoch: u64) -> Option<String> {
16 let last = indices
17 .iter()
18 .rfind(|idx| idx["epoch"].as_u64().map(|e| e < epoch).unwrap_or(false))?;
19 last["commitHash"].as_str().map(|s| s.to_string())
20}
21
22fn derive_from_scores(scores: &serde_json::Value) -> Option<Snapshot> {
26 let arr = scores.as_array()?;
27 let mut entries: Vec<(&serde_json::Value, i64)> = arr
29 .iter()
30 .filter_map(|s| {
31 let mu = s["mu"].as_i64()?;
32 Some((s, mu))
33 })
34 .collect();
35 entries.sort_by_key(|entry| std::cmp::Reverse(entry.1));
36
37 let ranking: Vec<serde_json::Value> = entries
38 .iter()
39 .filter_map(|(s, _)| s.get("commit").cloned())
40 .collect();
41
42 let variances: Vec<serde_json::Value> = arr
44 .iter()
45 .filter_map(|s| {
46 let commit = s.get("commit")?;
47 let sigma2 = s.get("sigma2")?;
48 Some(serde_json::json!([commit, sigma2]))
49 })
50 .collect();
51
52 Some(Snapshot {
53 ranking: serde_json::json!(ranking),
54 variances: Some(serde_json::json!(variances)),
55 })
56}
57
58pub fn find(
67 indices: &[serde_json::Value],
68 ranking_map: &serde_json::Value,
69 scores_map: &serde_json::Value,
70 epoch: u64,
71) -> Result<Option<Snapshot>, Box<dyn std::error::Error>> {
72 let commit_hash = match find_prior_commit_hash(indices, epoch) {
73 Some(h) => h,
74 None => return Ok(None),
75 };
76
77 if let Some(scores) = scores_map.get(&commit_hash)
79 && let Some(snapshot) = derive_from_scores(scores)
80 {
81 return Ok(Some(snapshot));
82 }
83
84 if let Some(ranking) = ranking_map.get(&commit_hash) {
87 return Ok(Some(Snapshot {
88 ranking: ranking.clone(),
89 variances: Some(serde_json::json!([])),
90 }));
91 }
92
93 Err(format!(
94 "cache stale: commit {} not found in ranking.json or scores.json",
95 &commit_hash[..8.min(commit_hash.len())]
96 )
97 .into())
98}
99
100#[cfg(test)]
101mod tests {
102 use super::*;
103
104 #[test]
105 fn test_find_no_prior_index() {
106 let indices: Vec<serde_json::Value> = vec![];
107 let ranking = serde_json::json!({});
108 let scores = serde_json::json!({});
109 assert!(find(&indices, &ranking, &scores, 1000).unwrap().is_none());
110 }
111
112 #[test]
113 fn test_find_all_future() {
114 let indices = vec![serde_json::json!({"commitHash": "abc", "epoch": 2000})];
115 let ranking = serde_json::json!({"abc": ["abc"]});
116 let scores = serde_json::json!({});
117 assert!(find(&indices, &ranking, &scores, 1000).unwrap().is_none());
118 }
119
120 #[test]
121 fn test_find_ranking_fallback() {
122 let indices = vec![
123 serde_json::json!({"commitHash": "aaa", "epoch": 100}),
124 serde_json::json!({"commitHash": "bbb", "epoch": 200}),
125 ];
126 let ranking = serde_json::json!({
127 "aaa": ["aaa"],
128 "bbb": ["bbb", "aaa"],
129 });
130 let scores = serde_json::json!({});
131 let snap = find(&indices, &ranking, &scores, 250).unwrap().unwrap();
132 assert_eq!(snap.ranking, serde_json::json!(["bbb", "aaa"]));
133 assert_eq!(snap.variances, Some(serde_json::json!([])));
134 }
135
136 #[test]
137 fn test_find_scores_preferred() {
138 let indices = vec![serde_json::json!({"commitHash": "aaa", "epoch": 100})];
139 let ranking = serde_json::json!({"aaa": ["aaa"]});
140 let scores = serde_json::json!({
141 "aaa": [
142 {"commit": "aaa", "mu": 500, "sigma2": 23000000}
143 ]
144 });
145 let snap = find(&indices, &ranking, &scores, 200).unwrap().unwrap();
146 assert_eq!(snap.ranking, serde_json::json!(["aaa"]));
147 assert!(snap.variances.is_some());
148 }
149
150 #[test]
151 fn test_find_scores_sorted_by_mu() {
152 let indices = vec![serde_json::json!({"commitHash": "aaa", "epoch": 100})];
153 let ranking = serde_json::json!({});
154 let scores = serde_json::json!({
155 "aaa": [
156 {"commit": "bbb", "mu": -100, "sigma2": 20000000},
157 {"commit": "aaa", "mu": 500, "sigma2": 23000000}
158 ]
159 });
160 let snap = find(&indices, &ranking, &scores, 200).unwrap().unwrap();
161 assert_eq!(snap.ranking, serde_json::json!(["aaa", "bbb"]));
163 }
164
165 #[test]
166 fn test_find_stale_cache_errors() {
167 let indices = vec![serde_json::json!({"commitHash": "abc12345", "epoch": 100})];
168 let ranking = serde_json::json!({});
169 let scores = serde_json::json!({});
170 let err = find(&indices, &ranking, &scores, 200).unwrap_err();
171 assert!(err.to_string().contains("cache stale"));
172 assert!(err.to_string().contains("abc12345"));
173 }
174}