Skip to main content

tauri_plugin_background_service/
error.rs

1//! Error types returned by background service operations.
2//!
3//! [`ServiceError`] is `#[non_exhaustive]` — new variants may be added in
4//! minor releases. Match with a wildcard `_` arm to avoid breakage.
5
6/// Errors that can occur during background service lifecycle.
7#[derive(Debug, thiserror::Error, Clone, serde::Serialize, serde::Deserialize)]
8#[non_exhaustive]
9pub enum ServiceError {
10    /// A service is already running; call `stopService()` first.
11    #[error("Service is already running")]
12    AlreadyRunning,
13
14    /// No service is currently running.
15    #[error("Service is not running")]
16    NotRunning,
17
18    /// The service's `init()` method failed.
19    #[error("Initialisation failed: {0}")]
20    Init(String),
21
22    /// A runtime error occurred inside the service's `run()` method.
23    #[error("Runtime error: {0}")]
24    Runtime(String),
25
26    /// A platform-specific error (e.g. Android foreground service denied).
27    #[error("Platform error: {0}")]
28    Platform(String),
29
30    /// Failed to install the OS service (desktop only).
31    #[cfg(feature = "desktop-service")]
32    #[error("Service installation failed: {0}")]
33    ServiceInstall(String),
34
35    /// Failed to uninstall the OS service (desktop only).
36    #[cfg(feature = "desktop-service")]
37    #[error("Service uninstallation failed: {0}")]
38    ServiceUninstall(String),
39
40    /// An IPC communication error (desktop only).
41    #[cfg(feature = "desktop-service")]
42    #[error("IPC error: {0}")]
43    Ipc(String),
44}
45
46#[cfg(test)]
47mod tests {
48    use super::*;
49
50    #[test]
51    fn display_already_running() {
52        assert_eq!(ServiceError::AlreadyRunning.to_string(), "Service is already running");
53    }
54
55    #[test]
56    fn display_not_running() {
57        assert_eq!(ServiceError::NotRunning.to_string(), "Service is not running");
58    }
59
60    #[test]
61    fn display_init() {
62        let msg = "db connection failed".to_string();
63        assert_eq!(
64            ServiceError::Init(msg.clone()).to_string(),
65            format!("Initialisation failed: {msg}")
66        );
67    }
68
69    #[test]
70    fn display_runtime() {
71        let msg = "network timeout".to_string();
72        assert_eq!(
73            ServiceError::Runtime(msg.clone()).to_string(),
74            format!("Runtime error: {msg}")
75        );
76    }
77
78    #[test]
79    fn display_platform() {
80        let msg = "foreground service denied".to_string();
81        assert_eq!(
82            ServiceError::Platform(msg.clone()).to_string(),
83            format!("Platform error: {msg}")
84        );
85    }
86
87    #[test]
88    fn convert_to_invoke_error_via_serialize() {
89        // ServiceError derives Serialize, so Tauri's blanket From<T: Serialize> for InvokeError applies.
90        // Verify the conversion compiles (type-level proof).
91        let err = ServiceError::Init("test".into());
92        let invoke_err: tauri::ipc::InvokeError = err.into();
93        // InvokeError wraps serde_json::Value — verify it contains the serialized form
94        let _val = &invoke_err.0;
95        assert!(!invoke_err.0.is_null());
96    }
97
98    #[test]
99    fn clone_roundtrip() {
100        let err = ServiceError::Init("test".into());
101        let cloned = err.clone();
102        assert_eq!(err.to_string(), cloned.to_string());
103    }
104
105    #[test]
106    fn serde_roundtrip_already_running() {
107        let err = ServiceError::AlreadyRunning;
108        let json = serde_json::to_string(&err).unwrap();
109        let de: ServiceError = serde_json::from_str(&json).unwrap();
110        assert!(matches!(de, ServiceError::AlreadyRunning));
111    }
112
113    #[test]
114    fn serde_roundtrip_init() {
115        let err = ServiceError::Init("boom".into());
116        let json = serde_json::to_string(&err).unwrap();
117        let de: ServiceError = serde_json::from_str(&json).unwrap();
118        assert!(matches!(de, ServiceError::Init(ref s) if s == "boom"));
119    }
120
121    #[cfg(feature = "desktop-service")]
122    mod desktop_service {
123        use super::*;
124
125        #[test]
126        fn display_service_install() {
127            let msg = "permission denied".to_string();
128            assert_eq!(
129                ServiceError::ServiceInstall(msg.clone()).to_string(),
130                format!("Service installation failed: {msg}")
131            );
132        }
133
134        #[test]
135        fn display_service_uninstall() {
136            let msg = "not found".to_string();
137            assert_eq!(
138                ServiceError::ServiceUninstall(msg.clone()).to_string(),
139                format!("Service uninstallation failed: {msg}")
140            );
141        }
142
143        #[test]
144        fn display_ipc_error() {
145            let msg = "connection lost".to_string();
146            assert_eq!(
147                ServiceError::Ipc(msg.clone()).to_string(),
148                format!("IPC error: {msg}")
149            );
150        }
151
152        #[test]
153        fn serde_roundtrip_service_install() {
154            let err = ServiceError::ServiceInstall("fail".into());
155            let json = serde_json::to_string(&err).unwrap();
156            let de: ServiceError = serde_json::from_str(&json).unwrap();
157            assert!(matches!(de, ServiceError::ServiceInstall(ref s) if s == "fail"));
158        }
159
160        #[test]
161        fn serde_roundtrip_service_uninstall() {
162            let err = ServiceError::ServiceUninstall("fail".into());
163            let json = serde_json::to_string(&err).unwrap();
164            let de: ServiceError = serde_json::from_str(&json).unwrap();
165            assert!(matches!(de, ServiceError::ServiceUninstall(ref s) if s == "fail"));
166        }
167
168        #[test]
169        fn serde_roundtrip_ipc() {
170            let err = ServiceError::Ipc("socket closed".into());
171            let json = serde_json::to_string(&err).unwrap();
172            let de: ServiceError = serde_json::from_str(&json).unwrap();
173            assert!(matches!(de, ServiceError::Ipc(ref s) if s == "socket closed"));
174        }
175
176        #[test]
177        fn clone_roundtrip_service_install() {
178            let err = ServiceError::ServiceInstall("fail".into());
179            let cloned = err.clone();
180            assert_eq!(err.to_string(), cloned.to_string());
181        }
182
183        #[test]
184        fn clone_roundtrip_service_uninstall() {
185            let err = ServiceError::ServiceUninstall("fail".into());
186            let cloned = err.clone();
187            assert_eq!(err.to_string(), cloned.to_string());
188        }
189
190        #[test]
191        fn clone_roundtrip_ipc() {
192            let err = ServiceError::Ipc("timeout".into());
193            let cloned = err.clone();
194            assert_eq!(err.to_string(), cloned.to_string());
195        }
196    }
197}