Skip to main content

mtl_foundation/
error.rs

1//! Error type for Foundation.
2//!
3//! Corresponds to `Foundation/NSError.hpp`.
4//!
5//! # C++ Equivalent
6//!
7//! ```cpp
8//! namespace NS {
9//! using ErrorDomain = class String*;
10//!
11//! _NS_CONST(ErrorDomain, CocoaErrorDomain);
12//! _NS_CONST(ErrorDomain, POSIXErrorDomain);
13//! _NS_CONST(ErrorDomain, OSStatusErrorDomain);
14//! _NS_CONST(ErrorDomain, MachErrorDomain);
15//!
16//! using ErrorUserInfoKey = class String*;
17//!
18//! _NS_CONST(ErrorUserInfoKey, UnderlyingErrorKey);
19//! _NS_CONST(ErrorUserInfoKey, LocalizedDescriptionKey);
20//! _NS_CONST(ErrorUserInfoKey, LocalizedFailureReasonErrorKey);
21//! _NS_CONST(ErrorUserInfoKey, LocalizedRecoverySuggestionErrorKey);
22//! _NS_CONST(ErrorUserInfoKey, LocalizedRecoveryOptionsErrorKey);
23//! _NS_CONST(ErrorUserInfoKey, RecoveryAttempterErrorKey);
24//! _NS_CONST(ErrorUserInfoKey, HelpAnchorErrorKey);
25//! _NS_CONST(ErrorUserInfoKey, DebugDescriptionErrorKey);
26//! _NS_CONST(ErrorUserInfoKey, LocalizedFailureErrorKey);
27//! _NS_CONST(ErrorUserInfoKey, StringEncodingErrorKey);
28//! _NS_CONST(ErrorUserInfoKey, URLErrorKey);
29//! _NS_CONST(ErrorUserInfoKey, FilePathErrorKey);
30//!
31//! class Error : public Copying<Error> {
32//! public:
33//!     static Error*     error(ErrorDomain domain, Integer code, class Dictionary* pDictionary);
34//!     static Error*     alloc();
35//!     Error*            init();
36//!     Error*            init(ErrorDomain domain, Integer code, class Dictionary* pDictionary);
37//!     Integer           code() const;
38//!     ErrorDomain       domain() const;
39//!     class Dictionary* userInfo() const;
40//!     class String*     localizedDescription() const;
41//!     class Array*      localizedRecoveryOptions() const;
42//!     class String*     localizedRecoverySuggestion() const;
43//!     class String*     localizedFailureReason() const;
44//! };
45//! }
46//! ```
47
48use std::ffi::c_void;
49use std::ptr::NonNull;
50
51use mtl_sys::{class, msg_send_0, msg_send_3, sel};
52
53use crate::array::Array;
54use crate::dictionary::Dictionary;
55use crate::object::{Copying, Referencing};
56use crate::string::String;
57use crate::types::Integer;
58
59/// Error domain type (alias for String pointer).
60///
61/// C++ equivalent: `NS::ErrorDomain`
62pub type ErrorDomain = *mut String;
63
64/// Error user info key type (alias for String pointer).
65///
66/// C++ equivalent: `NS::ErrorUserInfoKey`
67pub type ErrorUserInfoKey = *mut String;
68
69// Error domain constants - linked from Foundation framework
70#[link(name = "Foundation", kind = "framework")]
71unsafe extern "C" {
72    #[link_name = "NSCocoaErrorDomain"]
73    static COCOA_ERROR_DOMAIN: *mut c_void;
74
75    #[link_name = "NSPOSIXErrorDomain"]
76    static POSIX_ERROR_DOMAIN: *mut c_void;
77
78    #[link_name = "NSOSStatusErrorDomain"]
79    static OS_STATUS_ERROR_DOMAIN: *mut c_void;
80
81    #[link_name = "NSMachErrorDomain"]
82    static MACH_ERROR_DOMAIN: *mut c_void;
83
84    #[link_name = "NSUnderlyingErrorKey"]
85    static UNDERLYING_ERROR_KEY: *mut c_void;
86
87    #[link_name = "NSLocalizedDescriptionKey"]
88    static LOCALIZED_DESCRIPTION_KEY: *mut c_void;
89
90    #[link_name = "NSLocalizedFailureReasonErrorKey"]
91    static LOCALIZED_FAILURE_REASON_ERROR_KEY: *mut c_void;
92
93    #[link_name = "NSLocalizedRecoverySuggestionErrorKey"]
94    static LOCALIZED_RECOVERY_SUGGESTION_ERROR_KEY: *mut c_void;
95
96    #[link_name = "NSLocalizedRecoveryOptionsErrorKey"]
97    static LOCALIZED_RECOVERY_OPTIONS_ERROR_KEY: *mut c_void;
98
99    #[link_name = "NSRecoveryAttempterErrorKey"]
100    static RECOVERY_ATTEMPTER_ERROR_KEY: *mut c_void;
101
102    #[link_name = "NSHelpAnchorErrorKey"]
103    static HELP_ANCHOR_ERROR_KEY: *mut c_void;
104
105    #[link_name = "NSDebugDescriptionErrorKey"]
106    static DEBUG_DESCRIPTION_ERROR_KEY: *mut c_void;
107
108    #[link_name = "NSLocalizedFailureErrorKey"]
109    static LOCALIZED_FAILURE_ERROR_KEY: *mut c_void;
110
111    #[link_name = "NSStringEncodingErrorKey"]
112    static STRING_ENCODING_ERROR_KEY: *mut c_void;
113
114    #[link_name = "NSURLErrorKey"]
115    static URL_ERROR_KEY: *mut c_void;
116
117    #[link_name = "NSFilePathErrorKey"]
118    static FILE_PATH_ERROR_KEY: *mut c_void;
119}
120
121/// Get the Cocoa error domain.
122///
123/// C++ equivalent: `NS::CocoaErrorDomain`
124#[inline]
125pub fn cocoa_error_domain() -> ErrorDomain {
126    unsafe { COCOA_ERROR_DOMAIN as ErrorDomain }
127}
128
129/// Get the POSIX error domain.
130///
131/// C++ equivalent: `NS::POSIXErrorDomain`
132#[inline]
133pub fn posix_error_domain() -> ErrorDomain {
134    unsafe { POSIX_ERROR_DOMAIN as ErrorDomain }
135}
136
137/// Get the OSStatus error domain.
138///
139/// C++ equivalent: `NS::OSStatusErrorDomain`
140#[inline]
141pub fn os_status_error_domain() -> ErrorDomain {
142    unsafe { OS_STATUS_ERROR_DOMAIN as ErrorDomain }
143}
144
145/// Get the Mach error domain.
146///
147/// C++ equivalent: `NS::MachErrorDomain`
148#[inline]
149pub fn mach_error_domain() -> ErrorDomain {
150    unsafe { MACH_ERROR_DOMAIN as ErrorDomain }
151}
152
153/// Get the underlying error key.
154///
155/// C++ equivalent: `NS::UnderlyingErrorKey`
156#[inline]
157pub fn underlying_error_key() -> ErrorUserInfoKey {
158    unsafe { UNDERLYING_ERROR_KEY as ErrorUserInfoKey }
159}
160
161/// Get the localized description key.
162///
163/// C++ equivalent: `NS::LocalizedDescriptionKey`
164#[inline]
165pub fn localized_description_key() -> ErrorUserInfoKey {
166    unsafe { LOCALIZED_DESCRIPTION_KEY as ErrorUserInfoKey }
167}
168
169/// Get the localized failure reason error key.
170///
171/// C++ equivalent: `NS::LocalizedFailureReasonErrorKey`
172#[inline]
173pub fn localized_failure_reason_error_key() -> ErrorUserInfoKey {
174    unsafe { LOCALIZED_FAILURE_REASON_ERROR_KEY as ErrorUserInfoKey }
175}
176
177/// Get the localized recovery suggestion error key.
178///
179/// C++ equivalent: `NS::LocalizedRecoverySuggestionErrorKey`
180#[inline]
181pub fn localized_recovery_suggestion_error_key() -> ErrorUserInfoKey {
182    unsafe { LOCALIZED_RECOVERY_SUGGESTION_ERROR_KEY as ErrorUserInfoKey }
183}
184
185/// Get the localized recovery options error key.
186///
187/// C++ equivalent: `NS::LocalizedRecoveryOptionsErrorKey`
188#[inline]
189pub fn localized_recovery_options_error_key() -> ErrorUserInfoKey {
190    unsafe { LOCALIZED_RECOVERY_OPTIONS_ERROR_KEY as ErrorUserInfoKey }
191}
192
193/// Get the recovery attempter error key.
194///
195/// C++ equivalent: `NS::RecoveryAttempterErrorKey`
196#[inline]
197pub fn recovery_attempter_error_key() -> ErrorUserInfoKey {
198    unsafe { RECOVERY_ATTEMPTER_ERROR_KEY as ErrorUserInfoKey }
199}
200
201/// Get the help anchor error key.
202///
203/// C++ equivalent: `NS::HelpAnchorErrorKey`
204#[inline]
205pub fn help_anchor_error_key() -> ErrorUserInfoKey {
206    unsafe { HELP_ANCHOR_ERROR_KEY as ErrorUserInfoKey }
207}
208
209/// Get the debug description error key.
210///
211/// C++ equivalent: `NS::DebugDescriptionErrorKey`
212#[inline]
213pub fn debug_description_error_key() -> ErrorUserInfoKey {
214    unsafe { DEBUG_DESCRIPTION_ERROR_KEY as ErrorUserInfoKey }
215}
216
217/// Get the localized failure error key.
218///
219/// C++ equivalent: `NS::LocalizedFailureErrorKey`
220#[inline]
221pub fn localized_failure_error_key() -> ErrorUserInfoKey {
222    unsafe { LOCALIZED_FAILURE_ERROR_KEY as ErrorUserInfoKey }
223}
224
225/// Get the string encoding error key.
226///
227/// C++ equivalent: `NS::StringEncodingErrorKey`
228#[inline]
229pub fn string_encoding_error_key() -> ErrorUserInfoKey {
230    unsafe { STRING_ENCODING_ERROR_KEY as ErrorUserInfoKey }
231}
232
233/// Get the URL error key.
234///
235/// C++ equivalent: `NS::URLErrorKey`
236#[inline]
237pub fn url_error_key() -> ErrorUserInfoKey {
238    unsafe { URL_ERROR_KEY as ErrorUserInfoKey }
239}
240
241/// Get the file path error key.
242///
243/// C++ equivalent: `NS::FilePathErrorKey`
244#[inline]
245pub fn file_path_error_key() -> ErrorUserInfoKey {
246    unsafe { FILE_PATH_ERROR_KEY as ErrorUserInfoKey }
247}
248
249/// An Objective-C error object.
250///
251/// C++ equivalent: `NS::Error`
252#[repr(transparent)]
253#[derive(Clone)]
254pub struct Error(NonNull<c_void>);
255
256impl Error {
257    /// Create an error with the specified domain, code, and user info.
258    ///
259    /// C++ equivalent: `static Error* error(ErrorDomain domain, Integer code, class Dictionary* pDictionary)`
260    #[inline]
261    pub fn error(domain: ErrorDomain, code: Integer, user_info: *mut Dictionary) -> Option<Self> {
262        unsafe {
263            let ptr: *mut c_void = msg_send_3(
264                class!(NSError).as_ptr(),
265                sel!(errorWithDomain:code:userInfo:),
266                domain,
267                code,
268                user_info,
269            );
270            Self::from_ptr(ptr)
271        }
272    }
273
274    /// Allocate a new error.
275    ///
276    /// C++ equivalent: `static Error* alloc()`
277    #[inline]
278    pub fn alloc() -> Option<Self> {
279        unsafe {
280            let ptr: *mut c_void = msg_send_0(class!(NSError).as_ptr(), sel!(alloc));
281            Self::from_ptr(ptr)
282        }
283    }
284
285    /// Initialize an allocated error.
286    ///
287    /// C++ equivalent: `Error* init()`
288    #[inline]
289    pub fn init(&self) -> Option<Self> {
290        unsafe {
291            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(init));
292            Self::from_ptr(ptr)
293        }
294    }
295
296    /// Initialize with domain, code, and user info.
297    ///
298    /// C++ equivalent: `Error* init(ErrorDomain domain, Integer code, class Dictionary* pDictionary)`
299    #[inline]
300    pub fn init_with_domain(
301        &self,
302        domain: ErrorDomain,
303        code: Integer,
304        user_info: *mut Dictionary,
305    ) -> Option<Self> {
306        unsafe {
307            let ptr: *mut c_void = msg_send_3(
308                self.as_ptr(),
309                sel!(initWithDomain:code:userInfo:),
310                domain,
311                code,
312                user_info,
313            );
314            Self::from_ptr(ptr)
315        }
316    }
317
318    /// Get the error code.
319    ///
320    /// C++ equivalent: `Integer code() const`
321    #[inline]
322    pub fn code(&self) -> Integer {
323        unsafe { msg_send_0(self.as_ptr(), sel!(code)) }
324    }
325
326    /// Get the error domain.
327    ///
328    /// C++ equivalent: `ErrorDomain domain() const`
329    #[inline]
330    pub fn domain(&self) -> ErrorDomain {
331        unsafe { msg_send_0(self.as_ptr(), sel!(domain)) }
332    }
333
334    /// Get the user info dictionary.
335    ///
336    /// C++ equivalent: `class Dictionary* userInfo() const`
337    #[inline]
338    pub fn user_info(&self) -> *mut Dictionary {
339        unsafe { msg_send_0(self.as_ptr(), sel!(userInfo)) }
340    }
341
342    /// Get the localized description.
343    ///
344    /// C++ equivalent: `class String* localizedDescription() const`
345    #[inline]
346    pub fn localized_description(&self) -> *mut String {
347        unsafe { msg_send_0(self.as_ptr(), sel!(localizedDescription)) }
348    }
349
350    /// Get the localized recovery options.
351    ///
352    /// C++ equivalent: `class Array* localizedRecoveryOptions() const`
353    #[inline]
354    pub fn localized_recovery_options(&self) -> *mut Array<String> {
355        unsafe { msg_send_0(self.as_ptr(), sel!(localizedRecoveryOptions)) }
356    }
357
358    /// Get the localized recovery suggestion.
359    ///
360    /// C++ equivalent: `class String* localizedRecoverySuggestion() const`
361    #[inline]
362    pub fn localized_recovery_suggestion(&self) -> *mut String {
363        unsafe { msg_send_0(self.as_ptr(), sel!(localizedRecoverySuggestion)) }
364    }
365
366    /// Get the localized failure reason.
367    ///
368    /// C++ equivalent: `class String* localizedFailureReason() const`
369    #[inline]
370    pub fn localized_failure_reason(&self) -> *mut String {
371        unsafe { msg_send_0(self.as_ptr(), sel!(localizedFailureReason)) }
372    }
373
374    /// Create an Error from a raw pointer.
375    ///
376    /// # Safety
377    ///
378    /// The pointer must be a valid Objective-C NSError object.
379    #[inline]
380    pub unsafe fn from_ptr(ptr: *mut c_void) -> Option<Self> {
381        NonNull::new(ptr).map(Self)
382    }
383}
384
385impl Referencing for Error {
386    #[inline]
387    fn as_ptr(&self) -> *const c_void {
388        self.0.as_ptr()
389    }
390}
391
392impl Copying for Error {
393    #[inline]
394    fn copy(&self) -> Option<Self> {
395        unsafe {
396            let ptr: *mut c_void = msg_send_0(self.as_ptr(), sel!(copy));
397            Self::from_ptr(ptr)
398        }
399    }
400}
401
402// SAFETY: NSError is thread-safe for reference counting
403unsafe impl Send for Error {}
404unsafe impl Sync for Error {}
405
406impl std::fmt::Debug for Error {
407    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
408        f.debug_struct("Error")
409            .field("ptr", &self.0)
410            .field("code", &self.code())
411            .finish()
412    }
413}
414
415impl std::fmt::Display for Error {
416    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
417        let desc_ptr = self.localized_description();
418        if !desc_ptr.is_null() {
419            let desc = unsafe { &*desc_ptr };
420            if let Some(s) = desc.to_string() {
421                return write!(f, "{}", s);
422            }
423        }
424        write!(f, "Error(code={})", self.code())
425    }
426}
427
428impl std::error::Error for Error {}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn test_error_size() {
436        // Error should be pointer-sized
437        assert_eq!(
438            std::mem::size_of::<Error>(),
439            std::mem::size_of::<*mut c_void>()
440        );
441    }
442}