1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
//! A wrapper for `NSError`.
//!
//! It attempts to be thread safe where possible, and extract the "default" usable information out of
//! an `NSError`. This might not be what you need, though, so if it's missing something... well,
//! it's up for discussion.

use std::error;
use std::fmt;

use objc::{class, msg_send, sel, sel_impl};

use crate::foundation::{id, nil, NSInteger, NSString};

/// A wrapper around pieces of data extracted from `NSError`. This could be improved: right now, it
/// allocates `String` instances when theoretically it could be avoided, and we might be erasing
/// certain parts of the `NSError` object that are useful.
#[derive(Clone, Debug)]
pub struct Error {
    /// Represents the code. Some of these can be... archaic.
    pub code: usize,

    /// Represents the domain of the error.
    pub domain: String,

    /// Maps over to `[NSError localizedDescription]`.
    pub description: String
}

impl Error {
    /// Given an `NSError` (i.e, an id reference) we'll pull out the relevant information and
    /// configure this. We pull out the information as it makes the error thread safe this way,
    /// which is... easier, in some cases.
    pub fn new(error: id) -> Self {
        let (code, domain, description) = unsafe {
            let code: usize = msg_send![error, code];
            let domain = NSString::retain(msg_send![error, domain]);
            let description = NSString::retain(msg_send![error, localizedDescription]);

            (code, domain, description)
        };

        Error {
            code,
            domain: domain.to_string(),
            description: description.to_string()
        }
    }

    /// Returns a boxed `Error`.
    pub fn boxed(error: id) -> Box<Self> {
        Box::new(Error::new(error))
    }

    /// Used for cases where we need to return an `NSError` back to the system (e.g, top-level
    /// error handling). We just create a new `NSError` so the `Error` crate can be mostly
    /// thread safe.
    pub fn into_nserror(self) -> id {
        unsafe {
            let domain = NSString::new(&self.domain);
            let code = self.code as NSInteger;
            msg_send![class!(NSError), errorWithDomain:domain code:code userInfo:nil]
        }
    }
}

impl fmt::Display for Error {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}", self.description)
    }
}

impl error::Error for Error {}