screencapturekit/utils/
error.rs

1//! Error types for `ScreenCaptureKit`
2//!
3//! This module provides comprehensive error types for all operations in the library.
4//! All operations return [`SCResult<T>`] which is an alias for `Result<T, SCError>`.
5//!
6//! # Examples
7//!
8//! ```
9//! use screencapturekit::prelude::*;
10//!
11//! fn setup_capture() -> SCResult<()> {
12//!     // Configure with mutable configuration
13//!     let mut config = SCStreamConfiguration::default();
14//!     config.set_width(1920);
15//!     config.set_height(1080);
16//!     Ok(())
17//! }
18//!
19//! // Pattern matching on errors
20//! match setup_capture() {
21//!     Ok(_) => println!("Success!"),
22//!     Err(SCError::InvalidDimension { field, value }) => {
23//!         eprintln!("Invalid {}: {}", field, value);
24//!     }
25//!     Err(e) => eprintln!("Error: {}", e),
26//! }
27//! ```
28
29use std::fmt;
30
31/// Result type alias for `ScreenCaptureKit` operations
32///
33/// This is a convenience alias for `Result<T, SCError>` used throughout the library.
34///
35/// # Examples
36///
37/// ```
38/// use screencapturekit::prelude::*;
39///
40/// fn validate_dimensions(width: u32, height: u32) -> SCResult<()> {
41///     if width == 0 {
42///         return Err(SCError::invalid_dimension("width", 0));
43///     }
44///     if height == 0 {
45///         return Err(SCError::invalid_dimension("height", 0));
46///     }
47///     Ok(())
48/// }
49///
50/// assert!(validate_dimensions(0, 1080).is_err());
51/// assert!(validate_dimensions(1920, 1080).is_ok());
52/// ```
53pub type SCResult<T> = Result<T, SCError>;
54
55/// Comprehensive error type for `ScreenCaptureKit` operations
56///
57/// This enum covers all possible error conditions that can occur when using
58/// the `ScreenCaptureKit` API. Each variant provides specific context about
59/// what went wrong.
60///
61/// # Examples
62///
63/// ## Creating Errors
64///
65/// ```
66/// use screencapturekit::error::SCError;
67///
68/// // Using helper methods (recommended)
69/// let err = SCError::invalid_dimension("width", 0);
70/// assert_eq!(err.to_string(), "Invalid dimension: width must be greater than 0 (got 0)");
71///
72/// let err = SCError::permission_denied("Screen Recording");
73/// assert!(err.to_string().contains("Screen Recording"));
74/// ```
75///
76/// ## Pattern Matching
77///
78/// ```
79/// use screencapturekit::error::SCError;
80///
81/// fn handle_error(err: SCError) {
82///     match err {
83///         SCError::InvalidDimension { field, value } => {
84///             println!("Invalid {}: {}", field, value);
85///         }
86///         SCError::PermissionDenied(msg) => {
87///             println!("Permission needed: {}", msg);
88///         }
89///         _ => println!("Other error: {}", err),
90///     }
91/// }
92/// ```
93#[derive(Debug, Clone, PartialEq, Eq)]
94pub enum SCError {
95    /// Invalid configuration parameter
96    InvalidConfiguration(String),
97
98    /// Invalid dimension value (width or height)
99    InvalidDimension { field: String, value: usize },
100
101    /// Invalid pixel format
102    InvalidPixelFormat(String),
103
104    /// No shareable content available
105    NoShareableContent(String),
106
107    /// Display not found
108    DisplayNotFound(String),
109
110    /// Window not found
111    WindowNotFound(String),
112
113    /// Application not found
114    ApplicationNotFound(String),
115
116    /// Stream operation error
117    StreamError(String),
118
119    /// Stream already running
120    StreamAlreadyRunning,
121
122    /// Stream not running
123    StreamNotRunning,
124
125    /// Failed to start capture
126    CaptureStartFailed(String),
127
128    /// Failed to stop capture
129    CaptureStopFailed(String),
130
131    /// Buffer lock error
132    BufferLockError(String),
133
134    /// Buffer unlock error
135    BufferUnlockError(String),
136
137    /// Invalid buffer
138    InvalidBuffer(String),
139
140    /// Screenshot capture error
141    ScreenshotError(String),
142
143    /// Permission denied
144    PermissionDenied(String),
145
146    /// Feature not available on this macOS version
147    FeatureNotAvailable {
148        feature: String,
149        required_version: String,
150    },
151
152    /// FFI error
153    FFIError(String),
154
155    /// Null pointer encountered
156    NullPointer(String),
157
158    /// Timeout error
159    Timeout(String),
160
161    /// Generic internal error
162    InternalError(String),
163
164    /// OS error with code
165    OSError { code: i32, message: String },
166}
167
168impl fmt::Display for SCError {
169    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
170        match self {
171            Self::InvalidConfiguration(msg) => write!(f, "Invalid configuration: {msg}"),
172            Self::InvalidDimension { field, value } => {
173                write!(
174                    f,
175                    "Invalid dimension: {field} must be greater than 0 (got {value})"
176                )
177            }
178            Self::InvalidPixelFormat(msg) => write!(f, "Invalid pixel format: {msg}"),
179            Self::NoShareableContent(msg) => write!(f, "No shareable content available: {msg}"),
180            Self::DisplayNotFound(msg) => write!(f, "Display not found: {msg}"),
181            Self::WindowNotFound(msg) => write!(f, "Window not found: {msg}"),
182            Self::ApplicationNotFound(msg) => write!(f, "Application not found: {msg}"),
183            Self::StreamError(msg) => write!(f, "Stream error: {msg}"),
184            Self::StreamAlreadyRunning => write!(f, "Stream is already running"),
185            Self::StreamNotRunning => write!(f, "Stream is not running"),
186            Self::CaptureStartFailed(msg) => write!(f, "Failed to start capture: {msg}"),
187            Self::CaptureStopFailed(msg) => write!(f, "Failed to stop capture: {msg}"),
188            Self::BufferLockError(msg) => write!(f, "Failed to lock pixel buffer: {msg}"),
189            Self::BufferUnlockError(msg) => write!(f, "Failed to unlock pixel buffer: {msg}"),
190            Self::InvalidBuffer(msg) => write!(f, "Invalid buffer: {msg}"),
191            Self::ScreenshotError(msg) => write!(f, "Screenshot capture failed: {msg}"),
192            Self::PermissionDenied(msg) => {
193                write!(f, "Permission denied: {msg}. Check System Preferences → Security & Privacy → Screen Recording")
194            }
195            Self::FeatureNotAvailable {
196                feature,
197                required_version,
198            } => {
199                write!(
200                    f,
201                    "Feature not available: {feature} requires macOS {required_version}+"
202                )
203            }
204            Self::FFIError(msg) => write!(f, "FFI error: {msg}"),
205            Self::NullPointer(msg) => write!(f, "Null pointer: {msg}"),
206            Self::Timeout(msg) => write!(f, "Operation timed out: {msg}"),
207            Self::InternalError(msg) => write!(f, "Internal error: {msg}"),
208            Self::OSError { code, message } => write!(f, "OS error {code}: {message}"),
209        }
210    }
211}
212
213impl std::error::Error for SCError {}
214
215impl SCError {
216    /// Create an invalid configuration error
217    ///
218    /// # Examples
219    ///
220    /// ```
221    /// use screencapturekit::error::SCError;
222    ///
223    /// let err = SCError::invalid_config("Queue depth must be positive");
224    /// assert!(err.to_string().contains("Queue depth"));
225    /// ```
226    pub fn invalid_config(message: impl Into<String>) -> Self {
227        Self::InvalidConfiguration(message.into())
228    }
229
230    /// Create an invalid dimension error
231    ///
232    /// Use this when width or height validation fails.
233    ///
234    /// # Examples
235    ///
236    /// ```
237    /// use screencapturekit::error::SCError;
238    ///
239    /// let err = SCError::invalid_dimension("width", 0);
240    /// assert_eq!(
241    ///     err.to_string(),
242    ///     "Invalid dimension: width must be greater than 0 (got 0)"
243    /// );
244    ///
245    /// let err = SCError::invalid_dimension("height", 0);
246    /// assert!(err.to_string().contains("height"));
247    /// ```
248    pub fn invalid_dimension(field: impl Into<String>, value: usize) -> Self {
249        Self::InvalidDimension {
250            field: field.into(),
251            value,
252        }
253    }
254
255    /// Create a stream error
256    ///
257    /// # Examples
258    ///
259    /// ```
260    /// use screencapturekit::error::SCError;
261    ///
262    /// let err = SCError::stream_error("Failed to start");
263    /// assert!(err.to_string().contains("Stream error"));
264    /// ```
265    pub fn stream_error(message: impl Into<String>) -> Self {
266        Self::StreamError(message.into())
267    }
268
269    /// Create a permission denied error
270    ///
271    /// The error message automatically includes instructions to check System Preferences.
272    ///
273    /// # Examples
274    ///
275    /// ```
276    /// use screencapturekit::error::SCError;
277    ///
278    /// let err = SCError::permission_denied("Screen Recording");
279    /// let msg = err.to_string();
280    /// assert!(msg.contains("Screen Recording"));
281    /// assert!(msg.contains("System Preferences"));
282    /// ```
283    pub fn permission_denied(message: impl Into<String>) -> Self {
284        Self::PermissionDenied(message.into())
285    }
286
287    /// Create an FFI error
288    ///
289    /// Use for errors crossing the Rust/Swift boundary.
290    ///
291    /// # Examples
292    ///
293    /// ```
294    /// use screencapturekit::error::SCError;
295    ///
296    /// let err = SCError::ffi_error("Swift bridge call failed");
297    /// assert!(err.to_string().contains("FFI error"));
298    /// ```
299    pub fn ffi_error(message: impl Into<String>) -> Self {
300        Self::FFIError(message.into())
301    }
302
303    /// Create an internal error
304    ///
305    /// # Examples
306    ///
307    /// ```
308    /// use screencapturekit::error::SCError;
309    ///
310    /// let err = SCError::internal_error("Unexpected state");
311    /// assert!(err.to_string().contains("Internal error"));
312    /// ```
313    pub fn internal_error(message: impl Into<String>) -> Self {
314        Self::InternalError(message.into())
315    }
316
317    /// Create a null pointer error
318    ///
319    /// # Examples
320    ///
321    /// ```
322    /// use screencapturekit::error::SCError;
323    ///
324    /// let err = SCError::null_pointer("Display pointer");
325    /// assert!(err.to_string().contains("Null pointer"));
326    /// assert!(err.to_string().contains("Display pointer"));
327    /// ```
328    pub fn null_pointer(context: impl Into<String>) -> Self {
329        Self::NullPointer(context.into())
330    }
331
332    /// Create a feature not available error
333    ///
334    /// Use when a feature requires a newer macOS version.
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use screencapturekit::error::SCError;
340    ///
341    /// let err = SCError::feature_not_available("Screenshot Manager", "14.0");
342    /// let msg = err.to_string();
343    /// assert!(msg.contains("Screenshot Manager"));
344    /// assert!(msg.contains("14.0"));
345    /// ```
346    pub fn feature_not_available(feature: impl Into<String>, version: impl Into<String>) -> Self {
347        Self::FeatureNotAvailable {
348            feature: feature.into(),
349            required_version: version.into(),
350        }
351    }
352
353    /// Create a buffer lock error
354    ///
355    /// # Examples
356    ///
357    /// ```
358    /// use screencapturekit::error::SCError;
359    ///
360    /// let err = SCError::buffer_lock_error("Already locked");
361    /// assert!(err.to_string().contains("lock pixel buffer"));
362    /// ```
363    pub fn buffer_lock_error(message: impl Into<String>) -> Self {
364        Self::BufferLockError(message.into())
365    }
366
367    /// Create an OS error with error code
368    ///
369    /// # Examples
370    ///
371    /// ```
372    /// use screencapturekit::error::SCError;
373    ///
374    /// let err = SCError::os_error(-1, "System call failed");
375    /// let msg = err.to_string();
376    /// assert!(msg.contains("-1"));
377    /// assert!(msg.contains("System call failed"));
378    /// ```
379    pub fn os_error(code: i32, message: impl Into<String>) -> Self {
380        Self::OSError {
381            code,
382            message: message.into(),
383        }
384    }
385}
386
387// Legacy compatibility
388impl SCError {
389    /// Create from a message string (for backward compatibility)
390    ///
391    /// **Note:** Prefer using specific error constructors like [`SCError::invalid_config`]
392    /// or other helper methods for better error categorization.
393    ///
394    /// # Examples
395    ///
396    /// ```
397    /// use screencapturekit::error::SCError;
398    ///
399    /// // Old style (still works)
400    /// let err = SCError::new("Something went wrong");
401    /// assert!(err.to_string().contains("Something went wrong"));
402    /// ```
403    pub fn new(message: impl Into<String>) -> Self {
404        Self::InternalError(message.into())
405    }
406
407    /// Get the error message (for backward compatibility)
408    ///
409    /// **Note:** Prefer using [`ToString::to_string`] which provides the same functionality.
410    ///
411    /// # Examples
412    ///
413    /// ```
414    /// use screencapturekit::error::SCError;
415    ///
416    /// let err = SCError::invalid_dimension("width", 0);
417    /// let msg = err.message();
418    /// assert!(msg.contains("width"));
419    /// assert!(msg.contains("0"));
420    /// ```
421    pub fn message(&self) -> String {
422        self.to_string()
423    }
424}
425
426/// Helper function to create an error (for backward compatibility)
427///
428/// **Note:** Prefer using [`SCError::new`] or specific constructors.
429///
430/// # Examples
431///
432/// ```
433/// use screencapturekit::utils::error::create_sc_error;
434///
435/// let err = create_sc_error("Something failed");
436/// assert!(err.to_string().contains("Something failed"));
437/// ```
438pub fn create_sc_error(message: &str) -> SCError {
439    SCError::new(message)
440}