telemetry_kit/
error.rs

1//! Error types for telemetry-kit
2//!
3//! All errors include helpful context and actionable suggestions where possible.
4
5use thiserror::Error;
6
7/// Result type for telemetry operations
8pub type Result<T> = std::result::Result<T, TelemetryError>;
9
10/// Errors that can occur during telemetry operations
11#[derive(Debug, Error)]
12pub enum TelemetryError {
13    /// SQLite database error
14    ///
15    /// Common causes:
16    /// - Database file is locked (another process using it)
17    /// - Insufficient permissions to write to database file
18    /// - Disk full or quota exceeded
19    /// - Corrupted database file
20    ///
21    /// Suggestions:
22    /// - Check file permissions on the database directory
23    /// - Ensure no other process is using the database
24    /// - Try deleting the database file to recreate it (data will be lost)
25    #[error("Database error: {0}\n\nSuggestion: Check file permissions and ensure the database isn't locked by another process")]
26    Database(#[from] rusqlite::Error),
27
28    /// HTTP request error
29    ///
30    /// Common causes:
31    /// - Network connectivity issues
32    /// - Invalid endpoint URL
33    /// - Server is down or unreachable
34    /// - DNS resolution failure
35    /// - SSL/TLS certificate issues
36    ///
37    /// Suggestions:
38    /// - Check your internet connection
39    /// - Verify the endpoint URL is correct
40    /// - Check if the server is accessible via curl/ping
41    #[cfg(feature = "sync")]
42    #[error("HTTP request failed: {0}\n\nSuggestion: Check network connectivity and verify the endpoint URL")]
43    Http(#[from] reqwest::Error),
44
45    /// JSON serialization/deserialization error
46    ///
47    /// Common causes:
48    /// - Invalid JSON structure
49    /// - Schema version mismatch
50    /// - Missing required fields
51    /// - Type conversion errors
52    ///
53    /// Suggestions:
54    /// - Check that SDK and server versions are compatible
55    /// - Verify custom event data is valid JSON
56    #[error("JSON serialization error: {0}\n\nSuggestion: Ensure event data is valid JSON and SDK version matches server")]
57    Json(#[from] serde_json::Error),
58
59    /// Invalid configuration
60    ///
61    /// Suggestions:
62    /// - Review the configuration documentation
63    /// - Ensure all required fields are provided
64    /// - Check that UUIDs are in valid format
65    #[error("Invalid configuration: {0}\n\nSuggestion: Review configuration requirements in the documentation")]
66    InvalidConfig(String),
67
68    /// Authentication error
69    ///
70    /// Common causes:
71    /// - Invalid or expired token
72    /// - Incorrect HMAC secret
73    /// - Token doesn't have required permissions
74    ///
75    /// Suggestions:
76    /// - Verify your token and secret are correct
77    /// - Generate a new token if the current one is expired
78    /// - Check that the token has sync permissions
79    #[error("Authentication failed: {0}\n\nSuggestion: Verify your token and secret are correct")]
80    Auth(String),
81
82    /// Rate limit exceeded
83    ///
84    /// You've exceeded the rate limit for your tier.
85    ///
86    /// Suggestions:
87    /// - Wait the specified duration before retrying
88    /// - Reduce event frequency
89    /// - Batch events together
90    /// - Consider upgrading to a higher tier
91    #[error("Rate limit exceeded. Retry after {retry_after} seconds.\n\nSuggestion: Batch events together or upgrade your plan")]
92    RateLimitExceeded {
93        /// Seconds to wait before retrying
94        retry_after: u64,
95    },
96
97    /// Server error with status code
98    ///
99    /// The server encountered an error processing your request.
100    ///
101    /// Suggestions:
102    /// - If 5xx error, retry the request after a delay
103    /// - If 4xx error, check your request parameters
104    /// - Check server status page if available
105    #[error("Server error ({status}): {message}\n\nSuggestion: {}", Self::server_error_suggestion(*status))]
106    ServerError {
107        /// HTTP status code
108        status: u16,
109        /// Error message from server
110        message: String,
111    },
112
113    /// Maximum retries exceeded
114    ///
115    /// The operation failed after multiple retry attempts.
116    ///
117    /// Common causes:
118    /// - Persistent network issues
119    /// - Server is down
120    /// - Invalid credentials
121    ///
122    /// Suggestions:
123    /// - Check server health and network connectivity
124    /// - Verify authentication credentials
125    /// - Enable offline mode to queue events locally
126    #[error("Maximum retries exceeded\n\nSuggestion: Check server health and network connectivity, or enable offline mode")]
127    MaxRetriesExceeded,
128
129    /// Invalid event schema
130    ///
131    /// The event structure doesn't match the expected schema.
132    ///
133    /// Suggestions:
134    /// - Ensure SDK version is compatible with server
135    /// - Check that required event fields are present
136    /// - Review event schema documentation
137    #[error(
138        "Invalid event schema: {0}\n\nSuggestion: Ensure SDK version is compatible with server"
139    )]
140    InvalidSchema(String),
141
142    /// IO error
143    ///
144    /// Common causes:
145    /// - File or directory doesn't exist
146    /// - Insufficient permissions
147    /// - Disk full
148    ///
149    /// Suggestions:
150    /// - Check file/directory exists and is accessible
151    /// - Verify write permissions
152    /// - Ensure sufficient disk space
153    #[error("IO error: {0}\n\nSuggestion: Check file permissions and available disk space")]
154    Io(#[from] std::io::Error),
155
156    /// Machine ID error
157    ///
158    /// Failed to generate or retrieve a unique machine identifier.
159    ///
160    /// Common causes:
161    /// - System doesn't have machine UUID available
162    /// - Insufficient permissions to access machine ID
163    ///
164    /// Suggestions:
165    /// - This is usually safe to ignore (fallback ID will be used)
166    /// - On Docker/CI, this is expected behavior
167    #[error("Failed to get machine ID: {0}\n\nNote: This is normal in Docker/CI environments. A fallback ID will be used.")]
168    MachineId(String),
169
170    /// Generic error
171    #[error("{0}")]
172    Other(String),
173}
174
175impl TelemetryError {
176    /// Check if the error is retryable
177    pub fn is_retryable(&self) -> bool {
178        match self {
179            TelemetryError::RateLimitExceeded { .. } => true,
180            TelemetryError::ServerError { status, .. } if *status >= 500 => true,
181            _ => false,
182        }
183    }
184
185    /// Get actionable suggestion based on HTTP status code
186    fn server_error_suggestion(status: u16) -> &'static str {
187        match status {
188            400 => "Check request parameters and ensure they're valid",
189            401 => "Verify your authentication token and secret",
190            403 => "Your token doesn't have permission for this operation",
191            404 => "Check the endpoint URL - resource not found",
192            413 => "Request payload too large - reduce batch size",
193            429 => "Rate limited - wait before retrying or upgrade plan",
194            500..=599 => "Server error - retry with exponential backoff",
195            _ => "Check server logs for details",
196        }
197    }
198
199    /// Create a helpful InvalidConfig error with context
200    pub fn invalid_config(field: &str, reason: &str) -> Self {
201        Self::InvalidConfig(format!("{}: {}", field, reason))
202    }
203
204    /// Create a helpful InvalidConfig error for UUID validation
205    pub fn invalid_uuid(field: &str, value: &str) -> Self {
206        Self::InvalidConfig(format!(
207            "{} '{}' is not a valid UUID. Expected format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
208            field, value
209        ))
210    }
211
212    /// Create a helpful error for missing required fields
213    pub fn missing_field(field: &str) -> Self {
214        Self::InvalidConfig(format!("Missing required field: {}", field))
215    }
216}