//! `pic dead-letters` subcommands: `ls`, `show`, `replay`, `resolve`. use anyhow::Result; use crate::client::Client; use crate::config; use crate::output::{KvBlock, OutputMode, Table}; /// `pic dead-letters count --app ` — print the unresolved DL /// count for an app. Useful for headless probes (alerting / dashboards). pub async fn count(app: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let resp = client.dead_letters_count(app).await?; let mut table = Table::new(["unresolved"]); table.row([resp.unresolved.to_string()]); table.print(mode); Ok(()) } pub async fn ls(app: &str, unresolved: bool, limit: u32, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let resp = client.dead_letters_list(app, unresolved, limit).await?; let mut table = Table::new([ "id", "source", "op", "attempts", "resolved", "last_error", "created_at", ]); for d in resp.dead_letters { let last_err = truncate(&d.last_error, 60); let resolved = d.resolution.unwrap_or_else(|| "-".into()); table.row([ d.id, d.source, d.op, d.attempt_count.to_string(), resolved, last_err, d.created_at.to_rfc3339(), ]); } table.print(mode); Ok(()) } pub async fn show(app: &str, dl_id: &str, mode: OutputMode) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; let d = client.dead_letters_get(app, dl_id).await?; let mut block = KvBlock::new(); block .field("id", d.id) .field("source", d.source) .field("op", d.op) .field("trigger_id", d.trigger_id.unwrap_or_else(|| "-".into())) .field( "script_id", d.script_id .map(|s| s.to_string()) .unwrap_or_else(|| "-".into()), ) .field("attempt_count", d.attempt_count.to_string()) .field("first_attempt_at", d.first_attempt_at.to_rfc3339()) .field("last_attempt_at", d.last_attempt_at.to_rfc3339()) .field("last_error", d.last_error) .field("created_at", d.created_at.to_rfc3339()) .field( "resolved_at", d.resolved_at .map(|t| t.to_rfc3339()) .unwrap_or_else(|| "-".into()), ) .field("resolution", d.resolution.unwrap_or_else(|| "-".into())) .field("payload", d.payload.to_string()); block.print(mode); Ok(()) } pub async fn replay(app: &str, dl_id: &str) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; client.dead_letters_replay(app, dl_id).await?; println!("Replayed dead-letter {dl_id}"); Ok(()) } pub async fn resolve(app: &str, dl_id: &str, reason: &str) -> Result<()> { let creds = config::resolve()?; let client = Client::from_creds(&creds)?; client.dead_letters_resolve(app, dl_id, reason).await?; println!("Resolved dead-letter {dl_id}: {reason}"); Ok(()) } fn truncate(s: &str, max: usize) -> String { if s.chars().count() <= max { return s.to_string(); } let mut out: String = s.chars().take(max.saturating_sub(1)).collect(); out.push('…'); out }