aranet_cli/tui/
errors.rs

1//! User-friendly error message formatting.
2//!
3//! This module provides functions to convert technical BLE error messages
4//! into user-friendly text with actionable suggestions.
5
6/// Convert a technical error message to a user-friendly message with guidance.
7///
8/// Takes the raw error string and returns a tuple of (short_message, suggestion).
9pub fn format_error_with_guidance(error: &str) -> (String, Option<String>) {
10    let error_lower = error.to_lowercase();
11
12    // Characteristic not found (device issue) - check before generic "not found"
13    if error_lower.contains("characteristic not found") {
14        return (
15            "Device communication error".to_string(),
16            Some("The device may need a firmware update or may be incompatible.".to_string()),
17        );
18    }
19
20    // Device not found / no devices in range
21    if error_lower.contains("not found") || error_lower.contains("no devices") {
22        return (
23            "Device not found".to_string(),
24            Some(
25                "Make sure the device is powered on, in range, and not connected to another app."
26                    .to_string(),
27            ),
28        );
29    }
30
31    // No Bluetooth adapter
32    if error_lower.contains("no bluetooth adapter") || error_lower.contains("adapter unavailable") {
33        return (
34            "Bluetooth unavailable".to_string(),
35            Some("Check that Bluetooth is enabled in System Settings.".to_string()),
36        );
37    }
38
39    // Connection timeout
40    if error_lower.contains("timed out") || error_lower.contains("timeout") {
41        return (
42            "Connection timed out".to_string(),
43            Some("Move closer to the device or try again. The device may be busy.".to_string()),
44        );
45    }
46
47    // Device already connected
48    if error_lower.contains("already connected") {
49        return (
50            "Device busy".to_string(),
51            Some("The device may be connected to another app. Close other Bluetooth apps and try again.".to_string()),
52        );
53    }
54
55    // Permission denied
56    if error_lower.contains("permission") || error_lower.contains("access denied") {
57        return (
58            "Bluetooth permission denied".to_string(),
59            Some(
60                "Grant Bluetooth permissions in System Settings > Privacy & Security.".to_string(),
61            ),
62        );
63    }
64
65    // Connection rejected / pairing failed
66    if error_lower.contains("rejected") || error_lower.contains("pairing") {
67        return (
68            "Connection rejected".to_string(),
69            Some("Try removing the device from Bluetooth settings and reconnecting.".to_string()),
70        );
71    }
72
73    // Out of range
74    if error_lower.contains("out of range") {
75        return (
76            "Device out of range".to_string(),
77            Some(
78                "Move closer to the device (within 10 meters with clear line of sight)."
79                    .to_string(),
80            ),
81        );
82    }
83
84    // Invalid reading format
85    if error_lower.contains("invalid reading") || error_lower.contains("invalid data") {
86        return (
87            "Invalid sensor data".to_string(),
88            Some(
89                "The device returned unexpected data. Try refreshing or reconnecting.".to_string(),
90            ),
91        );
92    }
93
94    // Generic BLE error
95    if error_lower.contains("bluetooth error") || error_lower.contains("ble error") {
96        return (
97            "Bluetooth error".to_string(),
98            Some(
99                "Try disabling and re-enabling Bluetooth, or restart the application.".to_string(),
100            ),
101        );
102    }
103
104    // Default - show original error, no suggestion
105    (error.to_string(), None)
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn test_device_not_found() {
114        let (msg, suggestion) = format_error_with_guidance("Device 'Aranet4 12345' not found");
115        assert_eq!(msg, "Device not found");
116        assert!(suggestion.is_some());
117        assert!(suggestion.unwrap().contains("powered on"));
118    }
119
120    #[test]
121    fn test_no_devices() {
122        let (msg, suggestion) = format_error_with_guidance("No devices available in range");
123        assert_eq!(msg, "Device not found");
124        assert!(suggestion.is_some());
125    }
126
127    #[test]
128    fn test_timeout() {
129        let (msg, suggestion) = format_error_with_guidance("Connection timed out after 30s");
130        assert_eq!(msg, "Connection timed out");
131        assert!(suggestion.is_some());
132    }
133
134    #[test]
135    fn test_timeout_variant() {
136        let (msg, suggestion) = format_error_with_guidance("Operation timeout");
137        assert_eq!(msg, "Connection timed out");
138        assert!(suggestion.unwrap().contains("Move closer"));
139    }
140
141    #[test]
142    fn test_no_bluetooth_adapter() {
143        let (msg, suggestion) = format_error_with_guidance("No Bluetooth adapter available");
144        assert_eq!(msg, "Bluetooth unavailable");
145        assert!(suggestion.is_some());
146        assert!(suggestion.unwrap().contains("System Settings"));
147    }
148
149    #[test]
150    fn test_adapter_unavailable() {
151        let (msg, suggestion) = format_error_with_guidance("Bluetooth adapter unavailable");
152        assert_eq!(msg, "Bluetooth unavailable");
153        assert!(suggestion.is_some());
154    }
155
156    #[test]
157    fn test_device_already_connected() {
158        let (msg, suggestion) = format_error_with_guidance("Device is already connected");
159        assert_eq!(msg, "Device busy");
160        assert!(suggestion.is_some());
161        assert!(suggestion.unwrap().contains("other Bluetooth apps"));
162    }
163
164    #[test]
165    fn test_permission_denied() {
166        let (msg, suggestion) = format_error_with_guidance("Bluetooth permission denied");
167        assert_eq!(msg, "Bluetooth permission denied");
168        assert!(suggestion.is_some());
169        assert!(suggestion.unwrap().contains("Privacy & Security"));
170    }
171
172    #[test]
173    fn test_access_denied() {
174        let (msg, suggestion) = format_error_with_guidance("Access denied to Bluetooth");
175        assert_eq!(msg, "Bluetooth permission denied");
176        assert!(suggestion.is_some());
177    }
178
179    #[test]
180    fn test_connection_rejected() {
181        let (msg, suggestion) = format_error_with_guidance("Connection rejected by device");
182        assert_eq!(msg, "Connection rejected");
183        assert!(suggestion.is_some());
184        assert!(suggestion.unwrap().contains("removing the device"));
185    }
186
187    #[test]
188    fn test_pairing_failed() {
189        let (msg, suggestion) = format_error_with_guidance("Pairing failed with device");
190        assert_eq!(msg, "Connection rejected");
191        assert!(suggestion.is_some());
192    }
193
194    #[test]
195    fn test_out_of_range() {
196        let (msg, suggestion) = format_error_with_guidance("Device is out of range");
197        assert_eq!(msg, "Device out of range");
198        assert!(suggestion.is_some());
199        assert!(suggestion.unwrap().contains("10 meters"));
200    }
201
202    #[test]
203    fn test_characteristic_not_found() {
204        let (msg, suggestion) = format_error_with_guidance("Characteristic not found on device");
205        assert_eq!(msg, "Device communication error");
206        assert!(suggestion.is_some());
207        assert!(suggestion.unwrap().contains("firmware update"));
208    }
209
210    #[test]
211    fn test_invalid_reading() {
212        let (msg, suggestion) = format_error_with_guidance("Invalid reading received from sensor");
213        assert_eq!(msg, "Invalid sensor data");
214        assert!(suggestion.is_some());
215        assert!(suggestion.unwrap().contains("reconnecting"));
216    }
217
218    #[test]
219    fn test_invalid_data() {
220        let (msg, suggestion) = format_error_with_guidance("Invalid data format");
221        assert_eq!(msg, "Invalid sensor data");
222        assert!(suggestion.is_some());
223    }
224
225    #[test]
226    fn test_generic_bluetooth_error() {
227        let (msg, suggestion) = format_error_with_guidance("Bluetooth error occurred");
228        assert_eq!(msg, "Bluetooth error");
229        assert!(suggestion.is_some());
230        assert!(suggestion.unwrap().contains("re-enabling Bluetooth"));
231    }
232
233    #[test]
234    fn test_generic_ble_error() {
235        let (msg, suggestion) = format_error_with_guidance("BLE error: connection failed");
236        assert_eq!(msg, "Bluetooth error");
237        assert!(suggestion.is_some());
238    }
239
240    #[test]
241    fn test_unknown_error() {
242        let (msg, suggestion) = format_error_with_guidance("Some random error xyz");
243        assert_eq!(msg, "Some random error xyz");
244        assert!(suggestion.is_none());
245    }
246
247    #[test]
248    fn test_case_insensitivity() {
249        // Ensure matching works regardless of case
250        let (msg, _) = format_error_with_guidance("DEVICE NOT FOUND");
251        assert_eq!(msg, "Device not found");
252
253        let (msg, _) = format_error_with_guidance("TIMED OUT");
254        assert_eq!(msg, "Connection timed out");
255
256        let (msg, _) = format_error_with_guidance("OUT OF RANGE");
257        assert_eq!(msg, "Device out of range");
258    }
259}