Skip to main content

raps_cli/commands/
tracked.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2024-2025 Dmytro Yemelianov
3
4//! Tracked operation helper
5//!
6//! Wraps an async API call with a spinner (in interactive/Table mode) and appends
7//! timing + API health info on completion. Non-interactive and structured output
8//! modes (JSON, YAML, CSV) run the operation directly with no spinner.
9
10use anyhow::Result;
11use std::future::Future;
12use std::time::Instant;
13
14use crate::output::OutputFormat;
15use raps_kernel::api_health;
16
17/// Execute an async operation with progress tracking.
18///
19/// - **Table mode (interactive)**: Shows a spinner during the operation, then
20///   replaces it with a completion line including elapsed time and API health.
21/// - **Non-interactive / JSON / YAML / CSV**: Runs the operation directly with
22///   no visual feedback.
23/// - **On error**: Shows a failure indicator with elapsed time, then propagates
24///   the error.
25pub async fn tracked_op<F, Fut, T>(message: &str, output_format: OutputFormat, op: F) -> Result<T>
26where
27    F: FnOnce() -> Fut,
28    Fut: Future<Output = Result<T>>,
29{
30    if !output_format.supports_colors() {
31        return op().await;
32    }
33
34    let spinner = raps_kernel::progress::spinner(message);
35    let start = Instant::now();
36
37    match op().await {
38        Ok(result) => {
39            let elapsed = start.elapsed();
40            let snap = api_health::snapshot();
41
42            let status_suffix = if snap.sample_count > 0 {
43                format!(
44                    " ({}, avg: {}, API: {})",
45                    api_health::format_duration_ms(elapsed),
46                    api_health::format_duration_ms(snap.avg_latency),
47                    snap.health_status,
48                )
49            } else {
50                format!(" ({})", api_health::format_duration_ms(elapsed))
51            };
52
53            spinner.finish_with_message(format!("\u{2713} {}{}", message, status_suffix));
54            Ok(result)
55        }
56        Err(err) => {
57            let elapsed = start.elapsed();
58            spinner.finish_with_message(format!(
59                "\u{2717} {} (after {})",
60                message,
61                api_health::format_duration_ms(elapsed),
62            ));
63            Err(err)
64        }
65    }
66}