Skip to main content

tauri_plugin_device_info/
models.rs

1//! Data models for the device-info plugin.
2//!
3//! This module contains all the response types returned by the plugin's API.
4//! All types implement `Serialize` and `Deserialize` for JSON communication
5//! with the frontend, and use `camelCase` naming for JavaScript compatibility.
6
7use serde::{Deserialize, Serialize};
8
9// ============================================================================
10// Device Information
11// ============================================================================
12
13/// Comprehensive device identification and hardware information.
14///
15/// Contains unique identifiers and basic hardware details that can be used
16/// for device fingerprinting, analytics, or user identification.
17#[derive(Debug, Clone, Default, Deserialize, Serialize)]
18pub struct DeviceInfoResponse {
19    /// Unique device identifier (Hardware UUID or GUID)
20    pub uuid: Option<String>,
21    /// Device manufacturer (e.g., "Apple Inc.", "Dell Inc.")
22    pub manufacturer: Option<String>,
23    /// Device model name (e.g., "MacBook Pro", "OptiPlex 7090")
24    pub model: Option<String>,
25    /// Device serial number (may be restricted on some platforms)
26    pub serial: Option<String>,
27    /// Android-specific device ID (Android only)
28    pub android_id: Option<String>,
29    /// User-assigned device name (hostname)
30    pub device_name: Option<String>,
31}
32
33// ============================================================================
34// Battery Information
35// ============================================================================
36
37/// Battery status and health information.
38///
39/// Provides real-time battery metrics including charge level, charging state,
40/// and overall battery health assessment.
41#[derive(Debug, Clone, Default, Deserialize, Serialize)]
42#[serde(rename_all = "camelCase")]
43pub struct BatteryInfo {
44    /// Current battery charge level (0-100 percentage)
45    pub level: Option<f32>,
46    /// Whether the device is currently connected to power and charging
47    pub is_charging: Option<bool>,
48    /// Battery health status (e.g., "Good", "Fair", "Poor")
49    pub health: Option<String>,
50}
51
52// ============================================================================
53// Network Information
54// ============================================================================
55
56/// Network connection details and identifiers.
57///
58/// Contains information about the current network connection including
59/// IP address, connection type, and hardware address.
60#[derive(Debug, Clone, Default, Deserialize, Serialize)]
61#[serde(rename_all = "camelCase")]
62pub struct NetworkInfo {
63    /// Local IPv4 address (e.g., "192.168.1.100")
64    pub ip_address: Option<String>,
65    /// Connection type: "wifi", "ethernet", "cellular", or "unknown"
66    pub network_type: Option<String>,
67    /// MAC address (unavailable on iOS/Android due to privacy restrictions)
68    pub mac_address: Option<String>,
69}
70
71// ============================================================================
72// Storage Information
73// ============================================================================
74
75/// Storage capacity and type information.
76///
77/// Provides information about the primary storage device including
78/// total capacity, available space, and storage technology type.
79#[derive(Debug, Clone, Default, Deserialize, Serialize)]
80#[serde(rename_all = "camelCase")]
81pub struct StorageInfo {
82    /// Total storage capacity in bytes
83    pub total_space: u64,
84    /// Available (free) storage space in bytes
85    pub free_space: u64,
86    /// Storage technology type: "Ssd", "Hdd", "Removable", "Unknown"
87    pub storage_type: Option<String>,
88}
89
90// ============================================================================
91// Display Information
92// ============================================================================
93
94/// Display/screen properties and capabilities.
95///
96/// Contains information about the primary display including resolution,
97/// scaling factor, and refresh rate.
98#[derive(Debug, Clone, Default, Deserialize, Serialize)]
99#[serde(rename_all = "camelCase")]
100pub struct DisplayInfo {
101    /// Screen width in physical pixels
102    pub width: u32,
103    /// Screen height in physical pixels
104    pub height: u32,
105    /// Display scale factor (e.g., 2.0 for Retina/HiDPI displays)
106    pub scale_factor: f64,
107    /// Screen refresh rate in Hz (e.g., 60.0, 120.0, 144.0)
108    pub refresh_rate: Option<f64>,
109}
110
111// ============================================================================
112// Tests
113// ============================================================================
114
115#[cfg(test)]
116mod tests {
117    use super::*;
118
119    // ============ DeviceInfoResponse Tests ============
120
121    #[test]
122    fn device_info_response_default_is_all_none() {
123        let info = DeviceInfoResponse::default();
124        assert!(info.uuid.is_none());
125        assert!(info.manufacturer.is_none());
126        assert!(info.model.is_none());
127        assert!(info.serial.is_none());
128        assert!(info.android_id.is_none());
129        assert!(info.device_name.is_none());
130    }
131
132    #[test]
133    fn device_info_response_serializes_correctly() {
134        let info = DeviceInfoResponse {
135            uuid: Some("test-uuid".to_string()),
136            manufacturer: Some("Apple Inc.".to_string()),
137            model: Some("MacBook Pro".to_string()),
138            serial: Some("ABC123".to_string()),
139            android_id: None,
140            device_name: Some("My Mac".to_string()),
141        };
142
143        let json = serde_json::to_string(&info).unwrap();
144        assert!(json.contains("test-uuid"));
145        assert!(json.contains("Apple Inc."));
146        assert!(json.contains("MacBook Pro"));
147    }
148
149    #[test]
150    fn device_info_response_deserializes_correctly() {
151        let json = r#"{
152            "uuid": "12345",
153            "manufacturer": "Dell",
154            "model": "XPS 15",
155            "serial": null,
156            "android_id": null,
157            "device_name": "Work Laptop"
158        }"#;
159
160        let info: DeviceInfoResponse = serde_json::from_str(json).unwrap();
161        assert_eq!(info.uuid, Some("12345".to_string()));
162        assert_eq!(info.manufacturer, Some("Dell".to_string()));
163        assert!(info.serial.is_none());
164    }
165
166    // ============ BatteryInfo Tests ============
167
168    #[test]
169    fn battery_info_default_is_all_none() {
170        let battery = BatteryInfo::default();
171        assert!(battery.level.is_none());
172        assert!(battery.is_charging.is_none());
173        assert!(battery.health.is_none());
174    }
175
176    #[test]
177    fn battery_info_serializes_with_camel_case() {
178        let battery = BatteryInfo {
179            level: Some(85.0),
180            is_charging: Some(true),
181            health: Some("Good".to_string()),
182        };
183
184        let json = serde_json::to_string(&battery).unwrap();
185        // Check camelCase: isCharging not is_charging
186        assert!(json.contains("isCharging"));
187        assert!(!json.contains("is_charging"));
188        assert!(json.contains("85"));
189    }
190
191    #[test]
192    fn battery_info_deserializes_from_camel_case() {
193        let json = r#"{"level": 50.0, "isCharging": false, "health": "Good"}"#;
194        let battery: BatteryInfo = serde_json::from_str(json).unwrap();
195
196        assert_eq!(battery.level, Some(50.0));
197        assert_eq!(battery.is_charging, Some(false));
198    }
199
200    // ============ NetworkInfo Tests ============
201
202    #[test]
203    fn network_info_default_is_all_none() {
204        let network = NetworkInfo::default();
205        assert!(network.ip_address.is_none());
206        assert!(network.network_type.is_none());
207        assert!(network.mac_address.is_none());
208    }
209
210    #[test]
211    fn network_info_serializes_with_camel_case() {
212        let network = NetworkInfo {
213            ip_address: Some("192.168.1.100".to_string()),
214            network_type: Some("wifi".to_string()),
215            mac_address: Some("AA:BB:CC:DD:EE:FF".to_string()),
216        };
217
218        let json = serde_json::to_string(&network).unwrap();
219        assert!(json.contains("ipAddress"));
220        assert!(json.contains("networkType"));
221        assert!(json.contains("macAddress"));
222    }
223
224    // ============ StorageInfo Tests ============
225
226    #[test]
227    fn storage_info_default_has_zero_values() {
228        let storage = StorageInfo::default();
229        assert_eq!(storage.total_space, 0);
230        assert_eq!(storage.free_space, 0);
231        assert!(storage.storage_type.is_none());
232    }
233
234    #[test]
235    fn storage_info_serializes_with_camel_case() {
236        let storage = StorageInfo {
237            total_space: 500_000_000_000, // 500GB
238            free_space: 100_000_000_000,  // 100GB
239            storage_type: Some("Ssd".to_string()),
240        };
241
242        let json = serde_json::to_string(&storage).unwrap();
243        assert!(json.contains("totalSpace"));
244        assert!(json.contains("freeSpace"));
245        assert!(json.contains("storageType"));
246    }
247
248    // ============ DisplayInfo Tests ============
249
250    #[test]
251    fn display_info_default_has_zero_values() {
252        let display = DisplayInfo::default();
253        assert_eq!(display.width, 0);
254        assert_eq!(display.height, 0);
255        assert_eq!(display.scale_factor, 0.0);
256        assert!(display.refresh_rate.is_none());
257    }
258
259    #[test]
260    fn display_info_serializes_with_camel_case() {
261        let display = DisplayInfo {
262            width: 2560,
263            height: 1440,
264            scale_factor: 2.0,
265            refresh_rate: Some(60.0),
266        };
267
268        let json = serde_json::to_string(&display).unwrap();
269        assert!(json.contains("scaleFactor"));
270        assert!(json.contains("refreshRate"));
271        assert!(json.contains("2560"));
272    }
273
274    #[test]
275    fn display_info_deserializes_from_camel_case() {
276        let json = r#"{"width": 1920, "height": 1080, "scaleFactor": 1.0, "refreshRate": 144.0}"#;
277        let display: DisplayInfo = serde_json::from_str(json).unwrap();
278
279        assert_eq!(display.width, 1920);
280        assert_eq!(display.height, 1080);
281        assert_eq!(display.refresh_rate, Some(144.0));
282    }
283}