mithril_client_cli/commands/cardano_db_v2/
show.rs1use anyhow::{anyhow, Context};
2use clap::Parser;
3use cli_table::{print_stdout, Cell, CellStruct, Table};
4
5use crate::{
6 commands::{client_builder_with_fallback_genesis_key, SharedArgs},
7 utils::{CardanoDbUtils, ExpanderUtils},
8 CommandContext,
9};
10
11use mithril_client::{
12 common::{AncillaryLocation, DigestLocation, ImmutablesLocation, MultiFilesUri},
13 MithrilResult,
14};
15
16#[derive(Parser, Debug, Clone)]
18pub struct CardanoDbShowCommand {
19 #[clap(flatten)]
20 shared_args: SharedArgs,
21
22 hash: String,
24}
25
26impl CardanoDbShowCommand {
27 pub fn is_json_output_enabled(&self) -> bool {
29 self.shared_args.json
30 }
31
32 pub async fn execute(&self, context: CommandContext) -> MithrilResult<()> {
34 let params = context.config_parameters()?;
35 let client = client_builder_with_fallback_genesis_key(¶ms)?
36 .with_logger(context.logger().clone())
37 .build()?;
38
39 let get_list_of_artifact_ids = || async {
40 let cardano_dbs = client.cardano_database_v2().list().await.with_context(|| {
41 "Can not get the list of artifacts while retrieving the latest cardano db snapshot hash"
42 })?;
43
44 Ok(cardano_dbs
45 .iter()
46 .map(|cardano_db| cardano_db.hash.to_owned())
47 .collect::<Vec<String>>())
48 };
49
50 let cardano_db_message = client
51 .cardano_database_v2()
52 .get(
53 &ExpanderUtils::expand_eventual_id_alias(&self.hash, get_list_of_artifact_ids())
54 .await?,
55 )
56 .await?
57 .ok_or_else(|| anyhow!("Cardano DB snapshot not found for hash: '{}'", &self.hash))?;
58
59 if self.is_json_output_enabled() {
60 println!("{}", serde_json::to_string(&cardano_db_message)?);
61 } else {
62 let mut cardano_db_table = vec![
63 vec![
64 "Epoch".cell(),
65 format!("{}", &cardano_db_message.beacon.epoch).cell(),
66 ],
67 vec![
68 "Immutable File Number".cell(),
69 format!("{}", &cardano_db_message.beacon.immutable_file_number).cell(),
70 ],
71 vec!["Hash".cell(), cardano_db_message.hash.cell()],
72 vec!["Merkle root".cell(), cardano_db_message.merkle_root.cell()],
73 vec![
74 "Database size".cell(),
75 CardanoDbUtils::format_bytes_to_gigabytes(
76 cardano_db_message.total_db_size_uncompressed,
77 )
78 .cell(),
79 ],
80 vec![
81 "Cardano node version".cell(),
82 cardano_db_message.cardano_node_version.cell(),
83 ],
84 ];
85
86 cardano_db_table.append(&mut digest_location_rows(
87 &cardano_db_message.digests.locations,
88 ));
89
90 cardano_db_table.append(&mut immutables_location_rows(
91 &cardano_db_message.immutables.locations,
92 ));
93
94 cardano_db_table.append(&mut ancillary_location_rows(
95 &cardano_db_message.ancillary.locations,
96 ));
97
98 cardano_db_table.push(vec![
99 "Created".cell(),
100 cardano_db_message.created_at.to_string().cell(),
101 ]);
102
103 print_stdout(cardano_db_table.table())?;
104 }
105
106 Ok(())
107 }
108}
109
110fn digest_location_iter(locations: &[DigestLocation]) -> impl Iterator<Item = String> + use<'_> {
111 locations.iter().filter_map(|location| match location {
112 DigestLocation::Aggregator { uri } => Some(format!("Aggregator, uri: \"{}\"", uri)),
113 DigestLocation::CloudStorage {
114 uri,
115 compression_algorithm: _,
116 } => Some(format!("CloudStorage, uri: \"{}\"", uri)),
117 DigestLocation::Unknown => None,
118 })
119}
120
121fn digest_location_rows(locations: &[DigestLocation]) -> Vec<Vec<CellStruct>> {
122 format_location_rows("Digest location", digest_location_iter(locations))
123}
124
125fn immutables_location_iter(
126 locations: &[ImmutablesLocation],
127) -> impl Iterator<Item = String> + use<'_> {
128 locations.iter().filter_map(|location| match location {
129 ImmutablesLocation::CloudStorage {
130 uri,
131 compression_algorithm: _,
132 } => match uri {
133 MultiFilesUri::Template(template_uri) => Some(format!(
134 "CloudStorage, template_uri: \"{}\"",
135 template_uri.0
136 )),
137 },
138 ImmutablesLocation::Unknown => None,
139 })
140}
141
142fn immutables_location_rows(locations: &[ImmutablesLocation]) -> Vec<Vec<CellStruct>> {
143 format_location_rows("Immutables location", immutables_location_iter(locations))
144}
145
146fn ancillary_location_iter(
147 locations: &[AncillaryLocation],
148) -> impl Iterator<Item = String> + use<'_> {
149 locations.iter().filter_map(|location| match location {
150 AncillaryLocation::CloudStorage {
151 uri,
152 compression_algorithm: _,
153 } => Some(format!("CloudStorage, uri: \"{uri}\"")),
154 AncillaryLocation::Unknown => None,
155 })
156}
157
158fn ancillary_location_rows(locations: &[AncillaryLocation]) -> Vec<Vec<CellStruct>> {
159 format_location_rows("Ancillary location", ancillary_location_iter(locations))
160}
161
162fn format_location_rows(
163 location_name: &str,
164 locations: impl Iterator<Item = String>,
165) -> Vec<Vec<CellStruct>> {
166 locations
167 .enumerate()
168 .map(|(index, cell_content)| {
169 vec![
170 format!("{location_name} ({})", index + 1).cell(),
171 cell_content.cell(),
172 ]
173 })
174 .collect()
175}
176
177#[cfg(test)]
178mod tests {
179 use mithril_client::common::{CompressionAlgorithm, TemplateUri};
180
181 use super::*;
182
183 #[test]
184 fn digest_location_rows_when_no_uri_found() {
185 let rows = digest_location_rows(&[]);
186
187 assert!(rows.is_empty());
188 }
189
190 #[test]
191 fn digest_location_rows_when_uris_found() {
192 let locations = vec![
193 DigestLocation::Aggregator {
194 uri: "http://aggregator.net/".to_string(),
195 },
196 DigestLocation::CloudStorage {
197 uri: "http://cloudstorage.com/".to_string(),
198 compression_algorithm: None,
199 },
200 ];
201
202 let rows = digest_location_rows(&locations);
203 assert!(rows.len() == 2);
204
205 let table = rows.table();
206 let rows_rendered = table.display().unwrap().to_string();
207
208 assert!(rows_rendered.contains("Digest location (1)"));
209 assert!(rows_rendered.contains("CloudStorage, uri: \"http://cloudstorage.com/\""));
210 assert!(rows_rendered.contains("Digest location (2)"));
211 assert!(rows_rendered.contains("Aggregator, uri: \"http://aggregator.net/\""));
212 }
213
214 #[test]
215 fn digest_location_rows_display_and_count_only_known_location() {
216 let locations = vec![
217 DigestLocation::Unknown,
218 DigestLocation::CloudStorage {
219 uri: "http://cloudstorage.com/".to_string(),
220 compression_algorithm: None,
221 },
222 ];
223
224 let rows = digest_location_rows(&locations);
225 assert_eq!(1, rows.len());
226
227 let rows_rendered = rows.table().display().unwrap().to_string();
228 assert!(rows_rendered.contains("Digest location (1)"));
229 }
230
231 #[test]
232 fn immutables_location_rows_when_no_uri_found() {
233 let rows = immutables_location_rows(&[]);
234
235 assert!(rows.is_empty());
236 }
237
238 #[test]
239 fn immutables_location_row_returns_some_when_uri_found() {
240 let locations = vec![
241 ImmutablesLocation::CloudStorage {
242 uri: MultiFilesUri::Template(TemplateUri("http://cloudstorage1.com/".to_string())),
243 compression_algorithm: Some(CompressionAlgorithm::Gzip),
244 },
245 ImmutablesLocation::CloudStorage {
246 uri: MultiFilesUri::Template(TemplateUri("http://cloudstorage2.com/".to_string())),
247 compression_algorithm: Some(CompressionAlgorithm::Gzip),
248 },
249 ];
250
251 let rows = immutables_location_rows(&locations);
252
253 assert!(rows.len() == 2);
254
255 let table = rows.table();
256 let rows_rendered = table.display().unwrap().to_string();
257
258 assert!(rows_rendered.contains("Immutables location (1)"));
259 assert!(rows_rendered.contains("CloudStorage, template_uri: \"http://cloudstorage1.com/\""));
260 assert!(rows_rendered.contains("Immutables location (2)"));
261 assert!(rows_rendered.contains("CloudStorage, template_uri: \"http://cloudstorage2.com/\""));
262 }
263
264 #[test]
265 fn immutables_location_row_display_and_count_only_known_location() {
266 let locations = vec![
267 ImmutablesLocation::Unknown {},
268 ImmutablesLocation::CloudStorage {
269 uri: MultiFilesUri::Template(TemplateUri("http://cloudstorage2.com/".to_string())),
270 compression_algorithm: Some(CompressionAlgorithm::Gzip),
271 },
272 ];
273
274 let rows = immutables_location_rows(&locations);
275 assert_eq!(1, rows.len());
276
277 let rows_rendered = rows.table().display().unwrap().to_string();
278 assert!(rows_rendered.contains("Immutables location (1)"));
279 }
280
281 #[test]
282 fn ancillary_location_rows_when_no_uri_found() {
283 let rows = ancillary_location_rows(&[]);
284
285 assert!(rows.is_empty());
286 }
287
288 #[test]
289 fn ancillary_location_rows_when_uris_found() {
290 let locations = vec![
291 AncillaryLocation::CloudStorage {
292 uri: "http://cloudstorage1.com/".to_string(),
293 compression_algorithm: Some(CompressionAlgorithm::Gzip),
294 },
295 AncillaryLocation::CloudStorage {
296 uri: "http://cloudstorage2.com/".to_string(),
297 compression_algorithm: Some(CompressionAlgorithm::Gzip),
298 },
299 ];
300
301 let rows = ancillary_location_rows(&locations);
302
303 assert!(rows.len() == 2);
304
305 let table = rows.table();
306 let rows_rendered = table.display().unwrap().to_string();
307
308 assert!(rows_rendered.contains("Ancillary location (1)"));
309 assert!(rows_rendered.contains("CloudStorage, uri: \"http://cloudstorage1.com/\""));
310 assert!(rows_rendered.contains("Ancillary location (2)"));
311 assert!(rows_rendered.contains("CloudStorage, uri: \"http://cloudstorage2.com/\""));
312 }
313
314 #[test]
315 fn ancillary_location_rows_display_and_count_only_known_location() {
316 let locations = vec![
317 AncillaryLocation::Unknown {},
318 AncillaryLocation::CloudStorage {
319 uri: "http://cloudstorage2.com/".to_string(),
320 compression_algorithm: Some(CompressionAlgorithm::Gzip),
321 },
322 ];
323
324 let rows = ancillary_location_rows(&locations);
325 assert_eq!(1, rows.len());
326
327 let rows_rendered = rows.table().display().unwrap().to_string();
328 assert!(rows_rendered.contains("Ancillary location (1)"));
329 }
330}