Skip to main content

netspeed_cli/
common.rs

1//! Common shared utilities used across download, upload, formatting, and progress modules.
2//!
3//! This module consolidates duplicated functionality to follow DRY principles:
4//! - Bandwidth calculation
5//! - Stream count determination
6//! - Distance formatting
7//! - Data size formatting
8
9/// Calculate bandwidth in bits per second from bytes transferred and elapsed time.
10///
11/// # Examples
12///
13/// ```
14/// # use netspeed_cli::common::calculate_bandwidth;
15/// let bps = calculate_bandwidth(10_000_000, 2.0);
16/// assert_eq!(bps, 40_000_000.0);
17/// ```
18#[must_use]
19pub fn calculate_bandwidth(total_bytes: u64, elapsed_secs: f64) -> f64 {
20    if elapsed_secs > 0.0 {
21        (total_bytes as f64 * 8.0) / elapsed_secs
22    } else {
23        0.0
24    }
25}
26
27/// Determine number of concurrent streams based on single connection flag.
28///
29/// Returns 1 for single connection mode, 4 for multi-stream mode.
30///
31/// # Examples
32///
33/// ```
34/// # use netspeed_cli::common::determine_stream_count;
35/// assert_eq!(determine_stream_count(true), 1);
36/// assert_eq!(determine_stream_count(false), 4);
37/// ```
38#[must_use]
39pub fn determine_stream_count(single: bool) -> usize {
40    if single { 1 } else { 4 }
41}
42
43/// Format distance consistently: 1 decimal for < 100 km, 0 decimals for >= 100 km.
44///
45/// # Examples
46///
47/// ```
48/// # use netspeed_cli::common::format_distance;
49/// assert_eq!(format_distance(50.5), "50.5 km");
50/// assert_eq!(format_distance(150.5), "150 km");
51/// ```
52#[must_use]
53pub fn format_distance(km: f64) -> String {
54    if km < 100.0 {
55        format!("{km:.1} km")
56    } else {
57        format!("{km:.0} km")
58    }
59}
60
61/// Format byte count into a human-readable string (KB, MB, GB).
62///
63/// # Examples
64///
65/// ```
66/// # use netspeed_cli::common::format_data_size;
67/// assert!(format_data_size(512).contains("KB"));
68/// assert!(format_data_size(1_048_576).contains("MB"));
69/// assert!(format_data_size(1_073_741_824).contains("GB"));
70/// ```
71#[must_use]
72pub fn format_data_size(bytes: u64) -> String {
73    if bytes < 1024 * 1024 {
74        format!("{:.1} KB", bytes as f64 / 1024.0)
75    } else if bytes < 1024 * 1024 * 1024 {
76        format!("{:.1} MB", bytes as f64 / (1024.0 * 1024.0))
77    } else {
78        format!("{:.2} GB", bytes as f64 / (1024.0 * 1024.0 * 1024.0))
79    }
80}
81
82/// Validate an IPv4 address string.
83///
84/// # Examples
85///
86/// ```
87/// # use netspeed_cli::common::is_valid_ipv4;
88/// assert!(is_valid_ipv4("192.168.1.1"));
89/// assert!(!is_valid_ipv4("999.999.999.999"));
90/// ```
91#[must_use]
92pub fn is_valid_ipv4(s: &str) -> bool {
93    let parts: Vec<&str> = s.split('.').collect();
94    if parts.len() != 4 {
95        return false;
96    }
97    parts.iter().all(|p| p.parse::<u8>().is_ok())
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103
104    #[test]
105    fn test_calculate_bandwidth_normal() {
106        assert_eq!(calculate_bandwidth(10_000_000, 2.0), 40_000_000.0);
107    }
108
109    #[test]
110    fn test_calculate_bandwidth_zero_elapsed() {
111        assert_eq!(calculate_bandwidth(10_000_000, 0.0), 0.0);
112    }
113
114    #[test]
115    fn test_determine_stream_count_single() {
116        assert_eq!(determine_stream_count(true), 1);
117    }
118
119    #[test]
120    fn test_determine_stream_count_multi() {
121        assert_eq!(determine_stream_count(false), 4);
122    }
123
124    #[test]
125    fn test_format_distance_under_100() {
126        assert_eq!(format_distance(50.5), "50.5 km");
127        assert_eq!(format_distance(99.9), "99.9 km");
128    }
129
130    #[test]
131    fn test_format_distance_100_plus() {
132        assert_eq!(format_distance(100.0), "100 km");
133        assert_eq!(format_distance(150.5), "150 km");
134    }
135
136    #[test]
137    fn test_format_data_size_bytes() {
138        assert!(format_data_size(512).contains("KB"));
139    }
140
141    #[test]
142    fn test_format_data_size_kilobytes() {
143        assert!(format_data_size(500 * 1024).contains("KB"));
144    }
145
146    #[test]
147    fn test_format_data_size_megabytes() {
148        assert!(format_data_size(10 * 1024 * 1024).contains("MB"));
149    }
150
151    #[test]
152    fn test_format_data_size_gigabytes() {
153        assert!(format_data_size(4 * 1024 * 1024 * 1024).contains("GB"));
154    }
155
156    #[test]
157    fn test_is_valid_ipv4_valid() {
158        assert!(is_valid_ipv4("192.168.1.1"));
159        assert!(is_valid_ipv4("0.0.0.0"));
160        assert!(is_valid_ipv4("255.255.255.255"));
161    }
162
163    #[test]
164    fn test_is_valid_ipv4_invalid() {
165        assert!(!is_valid_ipv4("256.1.1.1"));
166        assert!(!is_valid_ipv4("1.2.3"));
167        assert!(!is_valid_ipv4("abc"));
168        assert!(!is_valid_ipv4(""));
169        assert!(!is_valid_ipv4("1.2.3.4.5"));
170    }
171}