diff --git a/examples/server_management.rhai b/examples/server_management.rhai index c244f80..ef7855e 100644 --- a/examples/server_management.rhai +++ b/examples/server_management.rhai @@ -1,9 +1,9 @@ // Get all servers and print them in a table let servers = hetzner.get_servers(); print(servers); -print_servers_table(servers); +servers.pretty_print(); // Get a specific server and print its details // Replace 1825193 with the server number you want to fetch let server = hetzner.get_server(1825193); -print_server_details(server); \ No newline at end of file +print(server); \ No newline at end of file diff --git a/examples/ssh_key_management.rhai b/examples/ssh_key_management.rhai index 7b3e48b..f6727ec 100644 --- a/examples/ssh_key_management.rhai +++ b/examples/ssh_key_management.rhai @@ -1,6 +1,6 @@ // Get all SSH keys and print them in a table let keys = hetzner.get_ssh_keys(); -print_ssh_keys_table(keys); +print(keys); // Get a specific SSH key // Replace "13:dc:a2:1e:a9:d2:1d:a9:39:f4:44:c5:f1:00:ec:c7" with the fingerprint of the key you want to fetch diff --git a/src/api/error.rs b/src/api/error.rs index e7c0786..1d51289 100644 --- a/src/api/error.rs +++ b/src/api/error.rs @@ -7,6 +7,6 @@ pub enum AppError { RequestError(#[from] reqwest::Error), #[error("API error: {0:?}")] ApiError(ApiError), - #[error("Deserialization failed: {0}\nResponse body: {1}")] - DeserializationError(String, String), + #[error("Deserialization Error: {0:?}")] + SerdeJsonError(#[from] serde_json::Error), } \ No newline at end of file diff --git a/src/api/mod.rs b/src/api/mod.rs index 5983742..51de4e6 100644 --- a/src/api/mod.rs +++ b/src/api/mod.rs @@ -1,9 +1,10 @@ pub mod error; pub mod models; +use std::any::type_name; + use self::models::{ - Boot, BootWrapper, ErrorResponse, Rescue, RescueWrapper, Server, ServerWrapper, SshKey, - SshKeyWrapper, + Boot, Rescue, Server, SshKey, }; use crate::config::Config; use error::AppError; @@ -28,23 +29,19 @@ impl Client { where T: serde::de::DeserializeOwned, { - match response.status() { - StatusCode::OK => { - // Read the body as text first, then try to deserialize from that - let text = response.text().unwrap_or_else(|_| "".to_string()); - let result = serde_json::from_str(&text); - match result { - Ok(val) => Ok(val), - Err(e) => { - let deser_err = format!("{:?}", e); - Err(AppError::DeserializationError(deser_err, text)) - } - } - }, - _status => { - let error_response: ErrorResponse = response.json()?; - Err(AppError::ApiError(error_response.error)) - } + let status = response.status(); + let body = response.text()?; + + println!("RESPONSE: \n{}", &body); + println!("Type of T to handle_response: {:#?}", type_name::()); + + if status == StatusCode::OK { + serde_json::from_str::(&body).map_err(Into::into) + } else { + Err(AppError::ApiError(models::ApiError { + status: status.as_u16(), + message: body, + })) } } @@ -58,8 +55,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let server_wrapper: ServerWrapper = self.handle_response(response)?; - Ok(server_wrapper.server) + self.handle_response(response) } pub fn get_servers(&self) -> Result, AppError> { @@ -69,9 +65,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let server_wrappers: Vec = self.handle_response(response)?; - let servers = server_wrappers.into_iter().map(|sw| sw.server).collect(); - Ok(servers) + self.handle_response(response) } pub fn get_ssh_keys(&self) -> Result, AppError> { let response = self @@ -80,9 +74,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let ssh_key_wrappers: Vec = self.handle_response(response)?; - let ssh_keys = ssh_key_wrappers.into_iter().map(|sw| sw.key).collect(); - Ok(ssh_keys) + self.handle_response(response) } pub fn get_ssh_key(&self, fingerprint: &str) -> Result { @@ -92,8 +84,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; - Ok(ssh_key_wrapper.key) + self.handle_response(response) } pub fn add_ssh_key(&self, name: &str, data: &str) -> Result { @@ -105,8 +96,7 @@ impl Client { .form(¶ms) .send()?; - let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; - Ok(ssh_key_wrapper.key) + self.handle_response(response) } pub fn update_ssh_key_name( @@ -122,8 +112,7 @@ impl Client { .form(¶ms) .send()?; - let ssh_key_wrapper: SshKeyWrapper = self.handle_response(response)?; - Ok(ssh_key_wrapper.key) + self.handle_response(response) } pub fn delete_ssh_key(&self, fingerprint: &str) -> Result<(), AppError> { @@ -141,8 +130,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let boot_wrapper: BootWrapper = self.handle_response(response)?; - Ok(boot_wrapper.boot) + self.handle_response(response) } pub fn get_rescue_boot_configuration( @@ -158,8 +146,7 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let rescue_wrapper: RescueWrapper = self.handle_response(response)?; - Ok(rescue_wrapper.rescue) + self.handle_response(response) } pub fn enable_rescue_mode( @@ -184,8 +171,7 @@ impl Client { .form(¶ms) .send()?; - let rescue_wrapper: RescueWrapper = self.handle_response(response)?; - Ok(rescue_wrapper.rescue) + self.handle_response(response) } pub fn disable_rescue_mode(&self, server_number: i32) -> Result { @@ -198,7 +184,6 @@ impl Client { .basic_auth(&self.config.username, Some(&self.config.password)) .send()?; - let rescue_wrapper: RescueWrapper = self.handle_response(response)?; - Ok(rescue_wrapper.rescue) + self.handle_response(response) } } \ No newline at end of file diff --git a/src/api/models.rs b/src/api/models.rs index 69e0d0f..4297f9f 100644 --- a/src/api/models.rs +++ b/src/api/models.rs @@ -1,6 +1,8 @@ use rhai::{CustomType, TypeBuilder}; use serde::{Deserialize, Deserializer}; use serde_json::Value; +use std::fmt; +use prettytable::{row, Table}; #[derive(Debug, Deserialize, Clone)] pub struct ServerWrapper { @@ -46,7 +48,43 @@ impl Server { .with_get("traffic", |s: &mut Server| s.traffic.clone()) .with_get("status", |s: &mut Server| s.status.clone()) .with_get("cancelled", |s: &mut Server| s.cancelled) - .with_get("paid_until", |s: &mut Server| s.paid_until.clone()); + .with_get("paid_until", |s: &mut Server| s.paid_until.clone()) + // when doing `print(server) in Rhai script, this will execute` + .on_print(|s: &mut Server| s.to_string()) + // also add the pretty_print function for convience + .with_fn("pretty_print", |s: &mut Server| s.to_string()); + } +} + +// Server should always be printed as a table, hence implement the Display trait to render the table +impl fmt::Display for Server { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + table.add_row(row!["Property", "Value"]); + table.add_row(row!["Server Number", self.server_number.to_string()]); + table.add_row(row!["Server Name", self.server_name.clone()]); + table.add_row(row!["Server IP", self.server_ip.clone()]); + table.add_row(row!["IPv6 Network", self.server_ipv6_net.clone()]); + table.add_row(row!["Product", self.product.clone()]); + table.add_row(row!["Datacenter", self.dc.clone()]); + table.add_row(row!["Traffic", self.traffic.clone()]); + table.add_row(row!["Status", self.status.clone()]); + table.add_row(row!["Cancelled", self.cancelled.to_string()]); + table.add_row(row!["Paid Until", self.paid_until.clone()]); + table.add_row(row!["Reset", self.reset.unwrap_or(false).to_string()]); + table.add_row(row!["Rescue", self.rescue.unwrap_or(false).to_string()]); + table.add_row(row!["VNC", self.vnc.unwrap_or(false).to_string()]); + table.add_row(row!["Windows", self.windows.is_some().to_string()]); + table.add_row(row!["Plesk", self.plesk.is_some().to_string()]); + table.add_row(row!["cPanel", self.cpanel.is_some().to_string()]); + table.add_row(row!["WOL", self.wol.unwrap_or(false).to_string()]); + table.add_row(row!["Hot Swap", self.hot_swap.unwrap_or(false).to_string()]); + table.add_row(row![ + "Linked Storagebox", + self.linked_storagebox + .map_or("N/A".to_string(), |id| id.to_string()) + ]); + write!(f, "{}", table) } } @@ -63,12 +101,16 @@ impl Subnet { builder .with_name("Subnet") .with_get("ip", |s: &mut Subnet| s.ip.clone()) - .with_get("mask", |s: &mut Subnet| s.mask.clone()); + .with_get("mask", |s: &mut Subnet| s.mask.clone()) + .on_print(|s: &mut Subnet| s.to_string()) + .with_fn("pretty_print", |s: &mut Subnet| s.to_string()); } } -#[derive(Debug, Deserialize, Clone)] -pub struct SshKeyWrapper { - pub key: SshKey, + +impl fmt::Display for Subnet { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "IP: {}, Mask: {}", self.ip, self.mask) + } } #[derive(Debug, Deserialize, Clone, CustomType)] @@ -92,7 +134,30 @@ impl SshKey { .with_get("key_type", |s: &mut SshKey| s.key_type.clone()) .with_get("size", |s: &mut SshKey| s.size) .with_get("data", |s: &mut SshKey| s.data.clone()) - .with_get("created_at", |s: &mut SshKey| s.created_at.clone()); + .with_get("created_at", |s: &mut SshKey| s.created_at.clone()) + .on_print(|s: &mut SshKey| s.to_string()) + .with_fn("pretty_print", |s: &mut SshKey| s.to_string()); + } +} + +impl fmt::Display for SshKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + table.add_row(row![ + "Name", + "Fingerprint", + "Type", + "Size", + "Created At" + ]); + table.add_row(row![ + self.name.clone(), + self.fingerprint.clone(), + self.key_type.clone(), + self.size.to_string(), + self.created_at.clone() + ]); + write!(f, "{}", table) } } #[derive(Debug, Deserialize, Clone)] @@ -120,13 +185,30 @@ impl Boot { .with_get("vnc", |b: &mut Boot| b.vnc.clone()) .with_get("windows", |b: &mut Boot| b.windows.clone()) .with_get("plesk", |b: &mut Boot| b.plesk.clone()) - .with_get("cpanel", |b: &mut Boot| b.cpanel.clone()); + .with_get("cpanel", |b: &mut Boot| b.cpanel.clone()) + .on_print(|b: &mut Boot| b.to_string()) + .with_fn("pretty_print", |b: &mut Boot| b.to_string()); } } -#[derive(Debug, Deserialize, Clone)] -pub struct RescueWrapper { - pub rescue: Rescue, +impl fmt::Display for Boot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut table = Table::new(); + table.add_row(row!["Configuration", "Details"]); + table.add_row(row!["Rescue", self.rescue.to_string()]); + table.add_row(row!["Linux", self.linux.to_string()]); + table.add_row(row!["VNC", self.vnc.to_string()]); + if let Some(windows) = &self.windows { + table.add_row(row!["Windows", windows.to_string()]); + } + if let Some(plesk) = &self.plesk { + table.add_row(row!["Plesk", plesk.to_string()]); + } + if let Some(cpanel) = &self.cpanel { + table.add_row(row!["cPanel", cpanel.to_string()]); + } + write!(f, "{}", table) + } } #[derive(Debug, Deserialize, Clone, CustomType)] @@ -158,7 +240,21 @@ impl Rescue { .with_get("authorized_key", |r: &mut Rescue| { r.authorized_key.clone() }) - .with_get("host_key", |r: &mut Rescue| r.host_key.clone()); + .with_get("host_key", |r: &mut Rescue| r.host_key.clone()) + .on_print(|r: &mut Rescue| r.to_string()) + .with_fn("pretty_print", |r: &mut Rescue| r.to_string()); + } +} + +impl fmt::Display for Rescue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "OS: {}, Active: {}, Keys: {}", + self.os.join(", "), + self.active, + self.authorized_key.join(", ") + ) } } @@ -194,7 +290,21 @@ impl Linux { .with_get("authorized_key", |l: &mut Linux| { l.authorized_key.clone() }) - .with_get("host_key", |l: &mut Linux| l.host_key.clone()); + .with_get("host_key", |l: &mut Linux| l.host_key.clone()) + .on_print(|l: &mut Linux| l.to_string()) + .with_fn("pretty_print", |l: &mut Linux| l.to_string()); + } +} + +impl fmt::Display for Linux { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Dist: {}, Lang: {}, Active: {}", + self.dist.join(", "), + self.lang.join(", "), + self.active + ) } } @@ -222,7 +332,21 @@ impl Vnc { .with_get("dist", |v: &mut Vnc| v.dist.clone()) .with_get("lang", |v: &mut Vnc| v.lang.clone()) .with_get("active", |v: &mut Vnc| v.active) - .with_get("password", |v: &mut Vnc| v.password.clone()); + .with_get("password", |v: &mut Vnc| v.password.clone()) + .on_print(|v: &mut Vnc| v.to_string()) + .with_fn("pretty_print", |v: &mut Vnc| v.to_string()); + } +} + +impl fmt::Display for Vnc { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Dist: {}, Lang: {}, Active: {}", + self.dist.join(", "), + self.lang.join(", "), + self.active + ) } } @@ -252,7 +376,21 @@ impl Windows { .with_get("dist", |w: &mut Windows| w.dist.clone()) .with_get("lang", |w: &mut Windows| w.lang.clone()) .with_get("active", |w: &mut Windows| w.active) - .with_get("password", |w: &mut Windows| w.password.clone()); + .with_get("password", |w: &mut Windows| w.password.clone()) + .on_print(|w: &mut Windows| w.to_string()) + .with_fn("pretty_print", |w: &mut Windows| w.to_string()); + } +} + +impl fmt::Display for Windows { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Dist: {}, Lang: {}, Active: {}", + self.dist.as_deref().unwrap_or_default().join(", "), + self.lang.as_deref().unwrap_or_default().join(", "), + self.active + ) } } @@ -284,7 +422,22 @@ impl Plesk { .with_get("lang", |p: &mut Plesk| p.lang.clone()) .with_get("active", |p: &mut Plesk| p.active) .with_get("password", |p: &mut Plesk| p.password.clone()) - .with_get("hostname", |p: &mut Plesk| p.hostname.clone()); + .with_get("hostname", |p: &mut Plesk| p.hostname.clone()) + .on_print(|p: &mut Plesk| p.to_string()) + .with_fn("pretty_print", |p: &mut Plesk| p.to_string()); + } +} + +impl fmt::Display for Plesk { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Dist: {}, Lang: {}, Active: {}, Hostname: {}", + self.dist.join(", "), + self.lang.join(", "), + self.active, + self.hostname.as_deref().unwrap_or("N/A") + ) } } @@ -316,7 +469,22 @@ impl Cpanel { .with_get("lang", |c: &mut Cpanel| c.lang.clone()) .with_get("active", |c: &mut Cpanel| c.active) .with_get("password", |c: &mut Cpanel| c.password.clone()) - .with_get("hostname", |c: &mut Cpanel| c.hostname.clone()); + .with_get("hostname", |c: &mut Cpanel| c.hostname.clone()) + .on_print(|c: &mut Cpanel| c.to_string()) + .with_fn("pretty_print", |c: &mut Cpanel| c.to_string()); + } +} + +impl fmt::Display for Cpanel { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "Dist: {}, Lang: {}, Active: {}, Hostname: {}", + self.dist.join(", "), + self.lang.join(", "), + self.active, + self.hostname.as_deref().unwrap_or("N/A") + ) } } @@ -369,14 +537,16 @@ where #[derive(Debug, Deserialize)] pub struct ApiError { #[allow(dead_code)] - pub status: i32, - #[allow(dead_code)] - pub code: String, + pub status: u16, #[allow(dead_code)] pub message: String, } -#[derive(Debug, Deserialize)] -pub struct ErrorResponse { - pub error: ApiError, +impl From for ApiError { + fn from(value: reqwest::blocking::Response) -> Self { + ApiError { + status: value.status().into(), + message: value.text().unwrap_or("The API call returned an error.".to_string()), + } + } } \ No newline at end of file diff --git a/src/scripting/mod.rs b/src/scripting/mod.rs index f762ba5..71fda3f 100644 --- a/src/scripting/mod.rs +++ b/src/scripting/mod.rs @@ -1,10 +1,11 @@ use crate::api::Client; -use crate::api::models::{Server, SshKey}; +use crate::api::models::{Rescue, Linux, Vnc, Windows, Plesk, Cpanel, Boot, Server, SshKey}; use rhai::{Engine, Scope}; pub mod server; pub mod ssh_keys; pub mod boot; +pub mod printing; pub fn setup_engine(client: Client) -> (Engine, Scope<'static>) { let mut engine = Engine::new(); @@ -12,17 +13,18 @@ pub fn setup_engine(client: Client) -> (Engine, Scope<'static>) { engine.build_type::(); engine.build_type::(); - engine.build_type::(); - engine.build_type::(); - engine.build_type::(); - engine.build_type::(); - engine.build_type::(); - engine.build_type::(); - engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); + engine.build_type::(); server::register(&mut engine); ssh_keys::register(&mut engine); boot::register(&mut engine); + printing::register(&mut engine); scope.push("hetzner", client); diff --git a/src/scripting/printing/mod.rs b/src/scripting/printing/mod.rs new file mode 100644 index 0000000..0c793c2 --- /dev/null +++ b/src/scripting/printing/mod.rs @@ -0,0 +1,29 @@ +use rhai::{Array, Engine}; +use crate::scripting::{Server, SshKey}; + +mod servers_table; +mod ssh_keys_table; + +pub fn pretty_print_dispatch(array: Array) { + if array.is_empty() { + println!(""); + return; + } + + let first = &array[0]; + + if first.is::() { + servers_table::pretty_print_servers(array); + } else if first.is::() { + ssh_keys_table::pretty_print_ssh_keys(array); + } else { + // Generic fallback for other types + for item in array { + println!("{}", item.to_string()); + } + } +} + +pub fn register(engine: &mut Engine) { + engine.register_fn("pretty_print", pretty_print_dispatch); +} diff --git a/src/scripting/printing/servers_table.rs b/src/scripting/printing/servers_table.rs new file mode 100644 index 0000000..dfd3dea --- /dev/null +++ b/src/scripting/printing/servers_table.rs @@ -0,0 +1,30 @@ +use prettytable::{row, Table}; +use rhai::Array; + +use super::Server; + +pub fn pretty_print_servers(servers: Array) { + let mut table = Table::new(); + table.add_row(row![b => + "Number", + "Name", + "IP", + "Product", + "DC", + "Status" + ]); + + for server_dyn in servers { + if let Some(server) = server_dyn.try_cast::() { + table.add_row(row![ + server.server_number.to_string(), + server.server_name, + server.server_ip, + server.product, + server.dc, + server.status + ]); + } + } + table.printstd(); +} \ No newline at end of file diff --git a/src/scripting/printing/ssh_keys_table.rs b/src/scripting/printing/ssh_keys_table.rs new file mode 100644 index 0000000..36c3a1c --- /dev/null +++ b/src/scripting/printing/ssh_keys_table.rs @@ -0,0 +1,26 @@ +use prettytable::{row, Table}; +use super::SshKey; + +pub fn pretty_print_ssh_keys(keys: rhai::Array) { + let mut table = Table::new(); + table.add_row(row![b => + "Name", + "Fingerprint", + "Type", + "Size", + "Created At" + ]); + + for key_dyn in keys { + if let Some(key) = key_dyn.try_cast::() { + table.add_row(row![ + key.name, + key.fingerprint, + key.key_type, + key.size.to_string(), + key.created_at + ]); + } + } + table.printstd(); +} diff --git a/src/scripting/server.rs b/src/scripting/server.rs index a532085..b217e02 100644 --- a/src/scripting/server.rs +++ b/src/scripting/server.rs @@ -30,63 +30,4 @@ pub mod server_api { Ok(servers.into_iter().map(Dynamic::from).collect()) } - #[rhai_fn(name = "print_servers_table")] - pub fn print_servers_table(servers: Array) { - let mut table = Table::new(); - table.add_row(row![ - "Server Number", - "Server Name", - "Server IP", - "Product", - "Datacenter", - "Status" - ]); - - for server_dynamic in servers { - let server: Server = server_dynamic - .try_cast::() - .expect("could not cast to server"); - table.add_row(row![ - server.server_number, - server.server_name, - server.server_ip, - server.product, - server.dc, - server.status - ]); - } - - table.printstd(); - } - - #[rhai_fn(name = "print_server_details")] - pub fn print_server_details(server: Server) { - let mut table = Table::new(); - table.add_row(row!["Property", "Value"]); - table.add_row(row!["Server Number", server.server_number]); - table.add_row(row!["Server Name", server.server_name]); - table.add_row(row!["Server IP", server.server_ip]); - table.add_row(row!["IPv6 Network", server.server_ipv6_net]); - table.add_row(row!["Product", server.product]); - table.add_row(row!["Datacenter", server.dc]); - table.add_row(row!["Traffic", server.traffic]); - table.add_row(row!["Status", server.status]); - table.add_row(row!["Cancelled", server.cancelled]); - table.add_row(row!["Paid Until", server.paid_until]); - table.add_row(row!["Reset", server.reset.unwrap_or(false)]); - table.add_row(row!["Rescue", server.rescue.unwrap_or(false)]); - table.add_row(row!["VNC", server.vnc.unwrap_or(false)]); - table.add_row(row!["Windows", server.windows.unwrap_or(false)]); - table.add_row(row!["Plesk", server.plesk.unwrap_or(false)]); - table.add_row(row!["cPanel", server.cpanel.unwrap_or(false)]); - table.add_row(row!["WOL", server.wol.unwrap_or(false)]); - table.add_row(row!["Hot Swap", server.hot_swap.unwrap_or(false)]); - table.add_row(row![ - "Linked Storagebox", - server - .linked_storagebox - .map_or("N/A".to_string(), |id| id.to_string()) - ]); - table.printstd(); - } } \ No newline at end of file diff --git a/src/scripting/ssh_keys.rs b/src/scripting/ssh_keys.rs index bb52514..cc84372 100644 --- a/src/scripting/ssh_keys.rs +++ b/src/scripting/ssh_keys.rs @@ -1,6 +1,6 @@ -use crate::api::{models::SshKey, Client}; -use prettytable::{row, Table}; -use rhai::{plugin::*, Array, Dynamic, Engine}; +use crate::api::{Client, models::SshKey}; +use prettytable::{Table, row}; +use rhai::{Array, Dynamic, Engine, plugin::*}; pub fn register(engine: &mut Engine) { let ssh_keys_module = exported_module!(ssh_keys_api); @@ -62,10 +62,10 @@ pub mod ssh_keys_api { .map_err(|e| e.to_string().into()) } - #[rhai_fn(name = "print_ssh_keys_table")] - pub fn print_ssh_keys_table(keys: Array) { + #[rhai_fn(name = "pretty_print")] + pub fn pretty_print_ssh_keys(keys: Array) { let mut table = Table::new(); - table.add_row(row![ + table.add_row(row![b => "Name", "Fingerprint", "Type", @@ -73,19 +73,17 @@ pub mod ssh_keys_api { "Created At" ]); - for key_dynamic in keys { - let key: SshKey = key_dynamic - .try_cast::() - .expect("could not cast to SshKey"); - table.add_row(row![ - key.name, - key.fingerprint, - key.key_type, - key.size, - key.created_at - ]); + for key_dyn in keys { + if let Some(key) = key_dyn.try_cast::() { + table.add_row(row![ + key.name, + key.fingerprint, + key.key_type, + key.size.to_string(), + key.created_at + ]); + } } - table.printstd(); } -} \ No newline at end of file +}