gestura_core_foundation/
error.rs

1//! Application error types for Gestura.
2//!
3//! This module provides foundational error types intended to be shared across the
4//! Gestura workspace.
5
6use thiserror::Error;
7
8/// Top-level application error for Gestura.
9///
10/// This error type encompasses common failure modes across the core library and
11/// domain crates, allowing for unified error handling across GUI and CLI.
12#[derive(Debug, Error)]
13pub enum AppError {
14    /// Generic I/O failure (file operations, etc.)
15    #[error("I/O error: {0}")]
16    Io(#[from] std::io::Error),
17
18    /// JSON serialization/deserialization error
19    #[error("JSON error: {0}")]
20    Json(#[from] serde_json::Error),
21
22    /// TOML parsing/serialization error
23    #[error("TOML error: {0}")]
24    Toml(String),
25
26    /// NATS messaging client error
27    #[error("NATS error: {0}")]
28    Nats(String),
29
30    /// BLE-related failures for haptic device communication
31    #[error("BLE error: {0}")]
32    Ble(String),
33
34    /// LLM-related failures (API errors, model not found, etc.)
35    #[error("LLM error: {0}")]
36    Llm(String),
37
38    /// Context window overflow - request exceeds model's context limit.
39    ///
40    /// This is distinct from generic LLM errors because it requires specific
41    /// recovery action (context compaction) rather than simple retry.
42    #[error("Context overflow: {0}")]
43    ContextOverflow(String),
44
45    /// HTTP client error (for API calls)
46    #[error("HTTP error: {0}")]
47    Http(#[from] reqwest::Error),
48
49    /// Voice/STT processing error
50    #[error("Voice error: {0}")]
51    Voice(String),
52
53    /// Audio capture/playback error
54    #[error("Audio error: {0}")]
55    Audio(String),
56
57    /// Configuration error (invalid settings, missing config, etc.)
58    #[error("Config error: {0}")]
59    Config(String),
60
61    /// Session management error
62    #[error("Session error: {0}")]
63    Session(String),
64
65    /// MCP (Model Context Protocol) error
66    #[error("MCP error: {0}")]
67    Mcp(String),
68
69    /// Permission denied error (for system operations)
70    #[error("Permission denied: {0}")]
71    PermissionDenied(String),
72
73    /// Resource not found error
74    #[error("Not found: {0}")]
75    NotFound(String),
76
77    /// Timeout error
78    #[error("Timeout: {0}")]
79    Timeout(String),
80
81    /// Invalid input or argument error
82    #[error("Invalid input: {0}")]
83    InvalidInput(String),
84
85    /// Internal error (unexpected state, etc.)
86    #[error("Internal error: {0}")]
87    Internal(String),
88}
89
90/// Result type alias for operations that may fail with [`AppError`].
91pub type Result<T> = std::result::Result<T, AppError>;
92
93/// Convert a TOML parsing error to [`AppError`].
94impl From<toml::de::Error> for AppError {
95    fn from(err: toml::de::Error) -> Self {
96        AppError::Toml(err.to_string())
97    }
98}
99
100/// Convert a TOML serialization error to [`AppError`].
101impl From<toml::ser::Error> for AppError {
102    fn from(err: toml::ser::Error) -> Self {
103        AppError::Toml(err.to_string())
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_io_error_conversion() {
113        let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
114        let app_err: AppError = io_err.into();
115        assert!(matches!(app_err, AppError::Io(_)));
116        assert!(app_err.to_string().contains("file not found"));
117    }
118
119    #[test]
120    fn test_llm_error() {
121        let err = AppError::Llm("API rate limit exceeded".to_string());
122        assert!(err.to_string().contains("API rate limit exceeded"));
123    }
124
125    #[test]
126    fn test_config_error() {
127        let err = AppError::Config("Missing API key".to_string());
128        assert!(err.to_string().contains("Missing API key"));
129    }
130
131    #[test]
132    fn test_result_type_alias() {
133        fn fallible_operation() -> Result<i32> {
134            Ok(42)
135        }
136        assert_eq!(fallible_operation().unwrap(), 42);
137    }
138}