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