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