Skip to main content

netspeed_cli/domain/
server.rs

1//! Server discovery and selection.
2//!
3//! This module provides the core server discovery and selection logic.
4
5use crate::error::Error;
6use crate::servers as impl_;
7use crate::types::{ClientLocation, Server};
8use reqwest::Client;
9
10/// Server discovery result.
11///
12/// Contains the list of available servers and the client's location.
13pub struct ServerDiscovery {
14    /// Available servers from speedtest.net
15    pub servers: Vec<Server>,
16    /// Client location from speedtest.net config API
17    pub client_location: Option<ClientLocation>,
18}
19
20/// Fetch available servers and client location.
21///
22/// # Errors
23///
24/// Returns [`Error::ServerListFetch`] if the server list cannot be fetched.
25pub async fn fetch(client: &Client) -> Result<ServerDiscovery, Error> {
26    let (servers, client_location) = impl_::fetch(client).await?;
27    Ok(ServerDiscovery {
28        servers,
29        client_location,
30    })
31}
32
33/// Fetch just the client location from speedtest.net.
34///
35/// # Errors
36///
37/// Returns [`Error::Context`] if location cannot be determined.
38pub async fn fetch_client_location(client: &Client) -> Result<ClientLocation, Error> {
39    impl_::fetch_client_location(client).await
40}
41
42/// Select the best server from a list based on distance.
43///
44/// Returns the server with the lowest distance to the client.
45///
46/// # Errors
47///
48/// Returns [`Error::ServerNotFound`] if the server list is empty.
49pub fn select_best_server(servers: &[Server]) -> Result<Server, Error> {
50    impl_::select_best_server(servers)
51}
52
53/// Run a ping test against a server.
54///
55/// Returns (average latency, jitter, packet_loss%, individual samples).
56///
57/// # Errors
58///
59/// Returns [`Error::NetworkError`] if all ping attempts fail.
60pub async fn ping_test(
61    client: &Client,
62    server: &Server,
63) -> Result<(f64, f64, f64, Vec<f64>), Error> {
64    impl_::ping_test(client, server).await
65}
66
67/// Calculate distance between two geographic points using Haversine formula.
68///
69/// # Examples
70///
71/// ```
72/// use netspeed_cli::domain::server::calculate_distance;
73/// let dist = calculate_distance(40.7128, -74.0060, 34.0522, -118.2437);
74/// assert!((dist - 3944.0).abs() < 200.0); // ~3944 km, NYC to LA
75/// ```
76pub fn calculate_distance(lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> f64 {
77    impl_::calculate_distance(lat1, lon1, lat2, lon2)
78}
79
80/// Measure latency while running a bandwidth test.
81///
82/// This runs in the background during download/upload tests to measure
83/// latency under load (bufferbloat detection).
84pub async fn measure_latency_under_load(
85    client: Client,
86    url: String,
87    samples: std::sync::Arc<std::sync::Mutex<Vec<f64>>>,
88    stop_signal: std::sync::Arc<std::sync::atomic::AtomicBool>,
89) {
90    impl_::measure_latency_under_load(client, url, samples, stop_signal).await
91}
92
93#[cfg(test)]
94mod tests {
95    use super::*;
96
97    #[test]
98    fn test_select_best_server_empty() {
99        let result = select_best_server(&[]);
100        assert!(result.is_err());
101    }
102
103    #[test]
104    fn test_calculate_distance_nyc_to_la() {
105        let dist = calculate_distance(40.7128, -74.0060, 34.0522, -118.2437);
106        assert!((dist - 3944.0).abs() < 200.0);
107    }
108
109    #[test]
110    fn test_calculate_distance_same_location() {
111        let dist = calculate_distance(40.7128, -74.0060, 40.7128, -74.0060);
112        assert!(dist < 1.0); // Should be essentially 0
113    }
114}