Skip to main content

speedtest/
models.rs

1// File: src\models.rs
2// Author: Hadi Cahyadi <cumulus13@gmail.com>
3// Date: 2026-02-09
4// Description: 
5// License: MIT
6
7use serde::{Deserialize, Serialize};
8// use std::collections::HashMap;
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct Client {
12    #[serde(default)]
13    pub ip: String,
14    #[serde(default)]
15    pub lat: String,
16    #[serde(default)]
17    pub lon: String,
18    #[serde(default)]
19    pub isp: String,
20    #[serde(default)]
21    pub country: String,
22    #[serde(default)]
23    pub isprating: String,
24    #[serde(default)]
25    pub rating: String,
26    #[serde(default)]
27    pub ispdlavg: String,
28    #[serde(default)]
29    pub ispulavg: String,
30    #[serde(default)]
31    pub loggedin: String,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct Server {
36    pub id: u32,
37    pub sponsor: String,
38    pub name: String,
39    pub country: String,
40    pub lat: f64,
41    pub lon: f64,
42    pub url: String,
43    #[serde(default)]
44    pub d: f64,
45    #[serde(default)]
46    pub latency: f64,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct Config {
51    pub client: Client,
52    pub ignore_servers: Vec<u32>,
53    pub sizes: Sizes,
54    pub counts: Counts,
55    pub threads: Threads,
56    pub length: Length,
57    pub upload_max: usize,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct Sizes {
62    pub upload: Vec<usize>,
63    pub download: Vec<usize>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct Counts {
68    pub upload: usize,
69    pub download: usize,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct Threads {
74    pub upload: usize,
75    pub download: usize,
76}
77
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct Length {
80    pub upload: u64,
81    pub download: u64,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct SpeedtestResults {
86    pub download: f64,
87    pub upload: f64,
88    pub ping: f64,
89    pub server: Server,
90    pub timestamp: String,
91    pub bytes_received: u64,
92    pub bytes_sent: u64,
93    #[serde(skip_serializing_if = "Option::is_none")]
94    pub share: Option<String>,
95    pub client: Client,
96}
97
98impl SpeedtestResults {
99    pub fn new(client: Client, server: Server) -> Self {
100        Self {
101            download: 0.0,
102            upload: 0.0,
103            ping: 0.0,
104            server,
105            timestamp: chrono::Utc::now().to_rfc3339(),
106            bytes_received: 0,
107            bytes_sent: 0,
108            share: None,
109            client,
110        }
111    }
112
113    pub fn to_csv(&self, delimiter: char) -> crate::error::Result<String> {
114        let mut wtr = csv::WriterBuilder::new()
115            .delimiter(delimiter as u8)
116            .from_writer(vec![]);
117
118        wtr.write_record(&[
119            self.server.id.to_string(),
120            self.server.sponsor.clone(),
121            self.server.name.clone(),
122            self.timestamp.clone(),
123            format!("{:.2}", self.server.d),
124            format!("{:.3}", self.ping),
125            format!("{:.2}", self.download),
126            format!("{:.2}", self.upload),
127            self.share.clone().unwrap_or_default(),
128            self.client.ip.clone(),
129        ])?;
130
131        let inner = wtr.into_inner().map_err(|e| {
132            crate::error::SpeedtestError::Csv(csv::Error::from(std::io::Error::new(
133                std::io::ErrorKind::Other,
134                format!("Failed to finalize CSV: {}", e)
135            )))
136        })?;
137        Ok(String::from_utf8_lossy(&inner).to_string())
138    }
139
140    pub fn csv_header(delimiter: char) -> crate::error::Result<String> {
141        let mut wtr = csv::WriterBuilder::new()
142            .delimiter(delimiter as u8)
143            .from_writer(vec![]);
144
145        wtr.write_record(&[
146            "Server ID",
147            "Sponsor",
148            "Server Name",
149            "Timestamp",
150            "Distance",
151            "Ping",
152            "Download",
153            "Upload",
154            "Share",
155            "IP Address",
156        ])?;
157
158        let inner = wtr.into_inner().map_err(|e| {
159            crate::error::SpeedtestError::Csv(csv::Error::from(std::io::Error::new(
160                std::io::ErrorKind::Other,
161                format!("Failed to finalize CSV: {}", e)
162            )))
163        })?;
164        Ok(String::from_utf8_lossy(&inner).to_string())
165    }
166
167    pub fn to_json(&self, pretty: bool) -> crate::error::Result<String> {
168        if pretty {
169            Ok(serde_json::to_string_pretty(self)?)
170        } else {
171            Ok(serde_json::to_string(self)?)
172        }
173    }
174}