|
1 | 1 | #[path = "default_values.rs"]
|
2 | 2 | mod default_values;
|
3 | 3 |
|
4 |
| -use std::{collections::BTreeMap, path::Path, sync::Arc}; |
| 4 | +use std::{ |
| 5 | + collections::BTreeMap, |
| 6 | + path::{Path, PathBuf}, |
| 7 | + sync::Arc, |
| 8 | +}; |
5 | 9 |
|
6 | 10 | use anyhow::{anyhow, Context};
|
7 | 11 | use serde_json::Value;
|
@@ -48,10 +52,12 @@ impl AppState {
|
48 | 52 | /// Construct the Application state by reading data from the given directory.
|
49 | 53 | /// This will fail if some files are missing or are inconsistent.
|
50 | 54 | pub fn from_directory(data_dir: &Path) -> StdResult<Self> {
|
| 55 | + let reader = DataDir::new(data_dir)?; |
51 | 56 | let epoch_settings = default_values::epoch_settings().to_owned();
|
52 |
| - let (certificate_list, certificates) = read_files(data_dir, "certificate", "hash")?; |
53 |
| - let (snapshot_list, snapshots) = read_files(data_dir, "snapshot", "digest")?; |
54 |
| - let (msd_list, msds) = read_files(data_dir, "mithril-stake-distribution", "hash")?; |
| 57 | + let (certificate_list, mut certificates) = reader.read_files("certificate", "hash")?; |
| 58 | + reader.read_certificate_chain(&certificate_list, &mut certificates)?; |
| 59 | + let (snapshot_list, snapshots) = reader.read_files("snapshot", "digest")?; |
| 60 | + let (msd_list, msds) = reader.read_files("mithril-stake-distribution", "hash")?; |
55 | 61 |
|
56 | 62 | let instance = Self {
|
57 | 63 | epoch_settings,
|
@@ -102,71 +108,130 @@ impl AppState {
|
102 | 108 | }
|
103 | 109 | }
|
104 | 110 |
|
105 |
| -/// Read related entity JSON files in the given directory. |
106 |
| -fn read_files( |
107 |
| - data_dir: &Path, |
108 |
| - entity: &str, |
109 |
| - field_id: &str, |
110 |
| -) -> StdResult<(String, BTreeMap<String, String>)> { |
111 |
| - debug!("Read data files, entity='{entity}', field='{field_id}'."); |
112 |
| - |
113 |
| - if !data_dir.exists() { |
114 |
| - return Err(anyhow!(format!( |
115 |
| - "Path '{}' does not exist.", |
116 |
| - data_dir.display() |
117 |
| - ))); |
118 |
| - } |
| 111 | +struct DataDir { |
| 112 | + data_dir: PathBuf, |
| 113 | +} |
| 114 | + |
| 115 | +impl DataDir { |
| 116 | + /// public constructor |
| 117 | + pub fn new(data_dir: &Path) -> StdResult<Self> { |
| 118 | + if !data_dir.exists() { |
| 119 | + return Err(anyhow!(format!( |
| 120 | + "Path '{}' does not exist.", |
| 121 | + data_dir.display() |
| 122 | + ))); |
| 123 | + } |
119 | 124 |
|
120 |
| - if !data_dir.is_dir() { |
121 |
| - return Err(anyhow!(format!( |
122 |
| - "Path '{}' is not a directory!", |
123 |
| - data_dir.display() |
124 |
| - ))); |
| 125 | + if !data_dir.is_dir() { |
| 126 | + return Err(anyhow!(format!( |
| 127 | + "Path '{}' is not a directory!", |
| 128 | + data_dir.display() |
| 129 | + ))); |
| 130 | + } |
| 131 | + |
| 132 | + let instance = Self { |
| 133 | + data_dir: data_dir.to_owned(), |
| 134 | + }; |
| 135 | + |
| 136 | + Ok(instance) |
125 | 137 | }
|
126 |
| - let list_file = { |
127 |
| - let list_file_name = format!("{entity}s.json"); |
128 |
| - |
129 |
| - data_dir.to_owned().join(list_file_name) |
130 |
| - }; |
131 |
| - trace!("Reading JSON list file '{}'.", list_file.display()); |
132 |
| - let list = std::fs::read_to_string(&list_file) |
133 |
| - .with_context(|| format!("Error while reading file '{}'.", list_file.display()))?; |
134 |
| - let list_json: Value = serde_json::from_str(&list) |
135 |
| - .with_context(|| format!("Could not parse JSON in file '{}'.", list_file.display()))?; |
136 |
| - let ids: Vec<String> = list_json |
137 |
| - .as_array() |
138 |
| - .ok_or_else(|| { |
139 |
| - anyhow!(format!( |
140 |
| - "List file for entity {entity} is not a JSON array." |
141 |
| - )) |
142 |
| - })? |
143 |
| - .iter() |
144 |
| - .map(|v| { |
145 |
| - v[field_id].as_str().map(|s| s.to_owned()).ok_or_else(|| { |
146 |
| - anyhow!(format!( |
147 |
| - "Field '{field_id}' for type '{entity}' did not return a string (value: '{}').", |
148 |
| - v.to_string() |
149 |
| - )) |
150 |
| - }) |
151 |
| - }) |
152 |
| - .collect::<StdResult<Vec<String>>>()?; |
153 | 138 |
|
154 |
| - let mut collection: BTreeMap<String, String> = BTreeMap::new(); |
| 139 | + fn read_list_file(&self, entity: &str) -> StdResult<(String, Value)> { |
| 140 | + let list_file = { |
| 141 | + let list_file_name = format!("{entity}s.json"); |
| 142 | + |
| 143 | + self.data_dir.to_owned().join(list_file_name) |
| 144 | + }; |
| 145 | + trace!("Reading JSON list file '{}'.", list_file.display()); |
| 146 | + let list = std::fs::read_to_string(&list_file) |
| 147 | + .with_context(|| format!("Error while reading file '{}'.", list_file.display()))?; |
| 148 | + let list_json: Value = serde_json::from_str(&list) |
| 149 | + .with_context(|| format!("Could not parse JSON in file '{}'.", list_file.display()))?; |
| 150 | + |
| 151 | + Ok((list, list_json)) |
| 152 | + } |
155 | 153 |
|
156 |
| - for id in ids { |
| 154 | + fn read_entity_file(&self, entity: &str, id: &str) -> StdResult<(String, Value)> { |
157 | 155 | let filename = format!("{entity}-{id}.json");
|
158 |
| - let path = data_dir.to_owned().join(&filename); |
| 156 | + let path = self.data_dir.to_owned().join(&filename); |
159 | 157 | trace!("Reading {entity} JSON file '{}'.", path.display());
|
160 | 158 | let content = std::fs::read_to_string(&path)
|
161 | 159 | .with_context(|| format!("Could not read entity file '{}'.", path.display()))?;
|
162 |
| - let _value: Value = serde_json::from_str(&content).with_context(|| { |
| 160 | + let value: Value = serde_json::from_str(&content).with_context(|| { |
163 | 161 | format!(
|
164 | 162 | "Entity file '{}' does not seem to hold valid JSON content.",
|
165 | 163 | path.display()
|
166 | 164 | )
|
167 | 165 | })?;
|
168 |
| - collection.insert(id, content); |
| 166 | + |
| 167 | + Ok((content, value)) |
169 | 168 | }
|
170 | 169 |
|
171 |
| - Ok((list, collection)) |
| 170 | + /// Read related entity JSON files in the given directory. |
| 171 | + pub fn read_files( |
| 172 | + &self, |
| 173 | + entity: &str, |
| 174 | + field_id: &str, |
| 175 | + ) -> StdResult<(String, BTreeMap<String, String>)> { |
| 176 | + debug!("Read data files, entity='{entity}', field='{field_id}'."); |
| 177 | + |
| 178 | + let (list, list_json) = self.read_list_file(entity)?; |
| 179 | + let ids: Vec<String> = list_json |
| 180 | + .as_array() |
| 181 | + .ok_or_else(|| { |
| 182 | + anyhow!(format!( |
| 183 | + "List file for entity {entity} is not a JSON array." |
| 184 | + )) |
| 185 | + })? |
| 186 | + .iter() |
| 187 | + .map(|v| { |
| 188 | + v[field_id].as_str().map(|s| s.to_owned()).ok_or_else(|| { |
| 189 | + anyhow!(format!( |
| 190 | + "Field '{field_id}' for type '{entity}' did not return a string (value: '{}').", |
| 191 | + v.to_string() |
| 192 | + )) |
| 193 | + }) |
| 194 | + }) |
| 195 | + .collect::<StdResult<Vec<String>>>()?; |
| 196 | + |
| 197 | + let mut collection: BTreeMap<String, String> = BTreeMap::new(); |
| 198 | + |
| 199 | + for id in &ids { |
| 200 | + let (content, _value) = self.read_entity_file(entity, id)?; |
| 201 | + collection.insert(id.to_owned(), content); |
| 202 | + } |
| 203 | + |
| 204 | + Ok((list, collection)) |
| 205 | + } |
| 206 | + |
| 207 | + pub fn read_certificate_chain( |
| 208 | + &self, |
| 209 | + certificate_list: &str, |
| 210 | + certificates: &mut BTreeMap<String, String>, |
| 211 | + ) -> StdResult<()> { |
| 212 | + trace!("fetching certificate chain"); |
| 213 | + let list = serde_json::from_str::<Value>(certificate_list)? |
| 214 | + .as_array() |
| 215 | + .map(|v| v.to_owned()) |
| 216 | + .ok_or_else(|| anyhow!("Could not cast certificates.json as JSON array."))?; |
| 217 | + let mut previous_hash = list[0]["previous_hash"] |
| 218 | + .as_str() |
| 219 | + .map(|v| v.to_owned()) |
| 220 | + .ok_or_else(|| anyhow!("Field 'previous_hash' does not exist in the first certificate of the certificatd list."))?; |
| 221 | + |
| 222 | + while previous_hash.len() > 0 { |
| 223 | + let (certificate, value) = self.read_entity_file("certificate", &previous_hash)?; |
| 224 | + let _ = certificates.insert(previous_hash.clone(), certificate); |
| 225 | + previous_hash = value["previous_hash"] |
| 226 | + .as_str() |
| 227 | + .map(|v| v.to_owned()) |
| 228 | + .ok_or_else(|| { |
| 229 | + anyhow!( |
| 230 | + "field 'previous_hash' does not exist in certificate id='{previous_hash}'." |
| 231 | + ) |
| 232 | + })?; |
| 233 | + } |
| 234 | + |
| 235 | + Ok(()) |
| 236 | + } |
172 | 237 | }
|
0 commit comments