Skip to main content

spider_util/
formatters.rs

1//! Formatting helpers for metrics and runtime output.
2
3use std::time::Duration;
4
5/// Trait for formatting duration values.
6pub trait DurationFormatter {
7    /// Formats a duration in a human-readable format.
8    fn formatted_duration(&self, duration: Duration) -> String;
9
10    /// Formats a request time, showing milliseconds or seconds as appropriate.
11    fn formatted_request_time(&self, duration: Option<Duration>) -> String;
12}
13
14/// Default implementation for duration formatting.
15pub struct DefaultDurationFormatter;
16
17fn format_human_duration(duration: Duration) -> String {
18    let total_secs = duration.as_secs();
19    let hours = total_secs / 3600;
20    let minutes = (total_secs % 3600) / 60;
21    let seconds = duration.as_secs_f64() % 60.0;
22
23    if hours > 0 {
24        format!("{hours}h {minutes:02}m {seconds:05.2}s")
25    } else if minutes > 0 {
26        format!("{minutes}m {seconds:05.2}s")
27    } else if total_secs > 0 {
28        format!("{:.2}s", duration.as_secs_f64())
29    } else if duration.as_millis() > 0 {
30        format!("{}ms", duration.as_millis())
31    } else {
32        format!("{}us", duration.as_micros())
33    }
34}
35
36impl DurationFormatter for DefaultDurationFormatter {
37    fn formatted_duration(&self, duration: Duration) -> String {
38        format_human_duration(duration)
39    }
40
41    fn formatted_request_time(&self, duration: Option<Duration>) -> String {
42        match duration {
43            Some(d) => {
44                if d.as_millis() < 1000 {
45                    format!("{} ms", d.as_millis())
46                } else {
47                    format!("{:.2} s", d.as_secs_f64())
48                }
49            }
50            None => "N/A".to_string(),
51        }
52    }
53}
54
55/// Trait for formatting byte values.
56pub trait ByteFormatter {
57    /// Formats a byte count in a human-readable format (B, KB, MB, GB).
58    fn formatted_bytes(&self, bytes: usize) -> String;
59}
60
61/// Default implementation for byte formatting.
62pub struct DefaultByteFormatter;
63
64impl ByteFormatter for DefaultByteFormatter {
65    fn formatted_bytes(&self, bytes: usize) -> String {
66        const KB: usize = 1024;
67        const MB: usize = 1024 * KB;
68        const GB: usize = 1024 * MB;
69
70        if bytes >= GB {
71            format!("{:.2} GB", bytes as f64 / GB as f64)
72        } else if bytes >= MB {
73            format!("{:.2} MB", bytes as f64 / MB as f64)
74        } else if bytes >= KB {
75            format!("{:.2} KB", bytes as f64 / KB as f64)
76        } else {
77            format!("{} B", bytes)
78        }
79    }
80}
81
82/// Trait for calculating rates.
83pub trait RateCalculator {
84    /// Calculates a rate given a count and elapsed time.
85    fn calculate_rate(&self, count: usize, elapsed: Duration) -> f64;
86}
87
88/// Default implementation for rate calculation.
89pub struct DefaultRateCalculator;
90
91impl RateCalculator for DefaultRateCalculator {
92    fn calculate_rate(&self, count: usize, elapsed: Duration) -> f64 {
93        let elapsed = elapsed.as_secs_f64();
94        if elapsed > 0.0 {
95            count as f64 / elapsed
96        } else {
97            0.0
98        }
99    }
100}
101
102// ============================================================================
103// Convenience Functions
104// ============================================================================
105
106/// Formats a duration in a human-readable format.
107pub fn format_duration(duration: Duration) -> String {
108    DefaultDurationFormatter.formatted_duration(duration)
109}
110
111/// Formats a request time, showing milliseconds or seconds as appropriate.
112pub fn format_request_time(duration: Option<Duration>) -> String {
113    DefaultDurationFormatter.formatted_request_time(duration)
114}
115
116/// Formats a byte count in a human-readable format.
117pub fn format_bytes(bytes: usize) -> String {
118    DefaultByteFormatter.formatted_bytes(bytes)
119}
120
121/// Calculates a rate given a count and elapsed time.
122pub fn calculate_rate(count: usize, elapsed: Duration) -> f64 {
123    DefaultRateCalculator.calculate_rate(count, elapsed)
124}