diff --git a/src/commands/apps.rs b/src/commands/apps.rs index 622aa7e..fe9ab01 100644 --- a/src/commands/apps.rs +++ b/src/commands/apps.rs @@ -1,8 +1,10 @@ -use crate::commands::{client_and_app_id, create_cloud_client, CommonArgs}; +use crate::commands::{apps_output::AppInfo, client_and_app_id, create_cloud_client, CommonArgs}; use anyhow::{Context, Result}; use clap::Parser; use cloud::{CloudClientInterface, DEFAULT_APPLIST_PAGE_SIZE}; -use cloud_openapi::models::{AppItem, AppItemPage, ValidationStatus}; +use cloud_openapi::models::{AppItem, ValidationStatus}; + +use super::apps_output::{print_app_info, print_app_list, OutputFormat}; #[derive(Parser, Debug)] #[clap(about = "Manage applications deployed to Fermyon Cloud")] @@ -19,6 +21,9 @@ pub enum AppsCommand { pub struct ListCommand { #[clap(flatten)] common: CommonArgs, + /// Desired output format + #[clap(value_enum, long = "format", default_value = "plain")] + format: OutputFormat, } #[derive(Parser, Debug)] @@ -35,6 +40,9 @@ pub struct InfoCommand { pub app: String, #[clap(flatten)] common: CommonArgs, + /// Desired output format + #[clap(value_enum, long = "format", default_value = "plain")] + format: OutputFormat, } impl AppsCommand { @@ -51,19 +59,21 @@ impl ListCommand { pub async fn run(self) -> Result<()> { let client = create_cloud_client(self.common.deployment_env_id.as_deref()).await?; let mut app_list_page = client.list_apps(DEFAULT_APPLIST_PAGE_SIZE, None).await?; - if app_list_page.total_items <= 0 { - eprintln!("No applications found"); - } else { - print_app_list(&app_list_page); - let mut page_index = 1; - while !app_list_page.is_last_page { - app_list_page = client - .list_apps(DEFAULT_APPLIST_PAGE_SIZE, Some(page_index)) - .await?; - print_app_list(&app_list_page); - page_index += 1; + let mut apps: Vec = vec![]; + let mut page_index = 1; + for app in app_list_page.items { + apps.push(app.name.clone()); + } + while !app_list_page.is_last_page { + app_list_page = client + .list_apps(DEFAULT_APPLIST_PAGE_SIZE, Some(page_index)) + .await?; + for app in app_list_page.items { + apps.push(app.name.clone()); } + page_index += 1; } + print_app_list(apps, self.format); Ok(()) } } @@ -92,13 +102,14 @@ impl InfoCommand { let (current_domain, in_progress_domain) = domains_current_and_in_progress(&app); - println!("Name: {}", &app.name); - print_if_present("Description: ", app.description.as_ref()); - print_if_present("URL: https://", current_domain); - if let Some(domain) = in_progress_domain { - println!("Validation for {} is in progress", domain); - }; + let info = AppInfo::new( + app.name.clone(), + app.description.clone(), + current_domain.cloned(), + in_progress_domain.is_none(), + ); + print_app_info(info, self.format); Ok(()) } } @@ -116,17 +127,3 @@ fn domains_current_and_in_progress(app: &AppItem) -> (Option<&String>, Option<&S None => (Some(auto_domain), None), } } - -fn print_if_present(prefix: &str, value: Option<&String>) { - if let Some(val) = value { - if !val.is_empty() { - println!("{prefix}{val}"); - } - } -} - -fn print_app_list(page: &AppItemPage) { - for app in &page.items { - println!("{}", app.name); - } -} diff --git a/src/commands/apps_output.rs b/src/commands/apps_output.rs new file mode 100644 index 0000000..a61eaf6 --- /dev/null +++ b/src/commands/apps_output.rs @@ -0,0 +1,82 @@ +use std::fmt::Display; + +use clap::ValueEnum; +use serde::Serialize; + +#[derive(Debug, ValueEnum, PartialEq, Clone)] +pub(crate) enum OutputFormat { + Plain, + Json, +} + +#[derive(Serialize)] +pub(crate) struct AppInfo { + name: String, + description: String, + url: Option, + #[serde(rename = "domainInfo")] + domain_info: DomainInfo, +} + +#[derive(Serialize)] +pub(crate) struct DomainInfo { + domain: Option, + #[serde(rename = "validationFinished")] + validation_finished: bool, +} + +impl AppInfo { + pub(crate) fn new( + name: String, + description: Option, + domain: Option, + domain_validation_finished: bool, + ) -> Self { + let url = domain.as_ref().map(|d| format!("https://{}", d)); + Self { + name, + description: description.unwrap_or_default(), + url, + domain_info: DomainInfo { + domain, + validation_finished: domain_validation_finished, + }, + } + } +} + +impl Display for AppInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + writeln!(f, "Name: {}", self.name)?; + if !self.description.is_empty() { + writeln!(f, "Description: {}", self.description)?; + } + if let Some(domain) = self.domain_info.domain.as_ref() { + writeln!(f, "URL: https://{}", domain)?; + if !self.domain_info.validation_finished { + writeln!(f, "Validation for {} is in progress", domain)?; + }; + } + Ok(()) + } +} + +pub(crate) fn print_app_list(apps: Vec, format: OutputFormat) { + match format { + OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&apps).unwrap()), + OutputFormat::Plain => { + if apps.is_empty() { + eprintln!("No applications found"); + return; + } + println!("{}", apps.join("\n")) + } + } +} + +pub(crate) fn print_app_info(app: AppInfo, format: OutputFormat) { + match format { + OutputFormat::Json => println!("{}", serde_json::to_string_pretty(&app).unwrap()), + OutputFormat::Plain => print!("{}", app), + } +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 29be8f3..b8f7590 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,4 +1,5 @@ pub mod apps; +pub mod apps_output; pub mod deploy; pub mod key_value; pub mod link;