Skip to content

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

objc2-foundation/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1212
`NSData` and `NSMutableData`.
1313
* Implemented `Extend` for `NSMutableArray`.
1414
* Add extra `Extend<&u8>` impl for `NSMutableData`.
15+
* Added `NSError`.
1516

1617
### Changed
1718
* Change selector syntax in `declare_class!` macro to be more Rust-like.

objc2-foundation/src/error.rs

+158
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
use core::fmt;
2+
use core::panic::{RefUnwindSafe, UnwindSafe};
3+
4+
use objc2::ffi::NSInteger;
5+
use objc2::rc::{Id, Shared};
6+
use objc2::{msg_send, msg_send_id};
7+
8+
use crate::{extern_class, NSCopying, NSDictionary, NSObject, NSString};
9+
10+
extern_class! {
11+
/// Information about an error condition including a domain, a
12+
/// domain-specific error code, and application-specific information.
13+
///
14+
/// See also Apple's [documentation on error handling][err], and their
15+
/// NSError [API reference][api].
16+
///
17+
/// [err]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ErrorHandlingCocoa/ErrorHandling/ErrorHandling.html#//apple_ref/doc/uid/TP40001806-CH201-SW1
18+
/// [api]: https://developer.apple.com/documentation/foundation/nserror?language=objc
19+
#[derive(PartialEq, Eq, Hash)]
20+
unsafe pub struct NSError: NSObject;
21+
}
22+
23+
// SAFETY: Error objects are immutable data containers.
24+
unsafe impl Sync for NSError {}
25+
unsafe impl Send for NSError {}
26+
27+
impl UnwindSafe for NSError {}
28+
impl RefUnwindSafe for NSError {}
29+
30+
pub type NSErrorUserInfoKey = NSString;
31+
pub type NSErrorDomain = NSString;
32+
33+
/// Creation methods.
34+
impl NSError {
35+
/// Construct a new [`NSError`] with the given code in the given domain.
36+
pub fn new(code: NSInteger, domain: &NSString) -> Id<Self, Shared> {
37+
unsafe { Self::with_user_info(code, domain, None) }
38+
}
39+
40+
// TODO: Figure out safety of `user_info` dict!
41+
unsafe fn with_user_info(
42+
code: NSInteger,
43+
domain: &NSString,
44+
user_info: Option<&NSDictionary<NSErrorUserInfoKey, NSObject>>,
45+
) -> Id<Self, Shared> {
46+
// SAFETY: `domain` and `user_info` are copied to the error object, so
47+
// even if the `&NSString` came from a `&mut NSMutableString`, we're
48+
// still good!
49+
unsafe {
50+
msg_send_id![
51+
msg_send_id![Self::class(), alloc],
52+
initWithDomain: domain,
53+
code: code,
54+
userInfo: user_info,
55+
]
56+
.expect("unexpected NULL NSError")
57+
}
58+
}
59+
}
60+
61+
/// Accessor methods.
62+
impl NSError {
63+
pub fn domain(&self) -> Id<NSString, Shared> {
64+
unsafe { msg_send_id![self, domain].expect("unexpected NULL NSError domain") }
65+
}
66+
67+
pub fn code(&self) -> NSInteger {
68+
unsafe { msg_send![self, code] }
69+
}
70+
71+
pub fn user_info(&self) -> Option<Id<NSDictionary<NSErrorUserInfoKey, NSObject>, Shared>> {
72+
unsafe { msg_send_id![self, userInfo] }
73+
}
74+
75+
pub fn localized_description(&self) -> Id<NSString, Shared> {
76+
unsafe {
77+
msg_send_id![self, localizedDescription].expect(
78+
"unexpected NULL localized description; a default should have been generated!",
79+
)
80+
}
81+
}
82+
83+
// TODO: localizedRecoveryOptions
84+
// TODO: localizedRecoverySuggestion
85+
// TODO: localizedFailureReason
86+
// TODO: helpAnchor
87+
// TODO: +setUserInfoValueProviderForDomain:provider:
88+
// TODO: +userInfoValueProviderForDomain:
89+
90+
// TODO: recoveryAttempter
91+
// TODO: attemptRecoveryFromError:...
92+
93+
// TODO: Figure out if this is a good design, or if we should do something
94+
// differently (like a Rusty name for the function, or putting a bunch of
95+
// statics in a module instead)?
96+
#[allow(non_snake_case)]
97+
pub fn NSLocalizedDescriptionKey() -> &'static NSErrorUserInfoKey {
98+
extern "C" {
99+
#[link_name = "NSLocalizedDescriptionKey"]
100+
static VALUE: &'static NSErrorUserInfoKey;
101+
}
102+
unsafe { VALUE }
103+
}
104+
105+
// TODO: Other NSErrorUserInfoKey values
106+
// TODO: NSErrorDomain values
107+
}
108+
109+
impl fmt::Debug for NSError {
110+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111+
f.debug_struct("NSError")
112+
.field("domain", &self.domain())
113+
.field("code", &self.code())
114+
.field("user_info", &self.user_info())
115+
.finish()
116+
}
117+
}
118+
119+
impl fmt::Display for NSError {
120+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
121+
write!(f, "{}", self.localized_description())
122+
}
123+
}
124+
125+
impl std::error::Error for NSError {}
126+
127+
unsafe impl NSCopying for NSError {
128+
type Ownership = Shared;
129+
type Output = Self;
130+
}
131+
132+
#[cfg(test)]
133+
mod tests {
134+
use super::*;
135+
use alloc::format;
136+
137+
use crate::ns_string;
138+
139+
#[test]
140+
fn custom_domain() {
141+
let error = NSError::new(42, ns_string!("MyDomain"));
142+
assert_eq!(error.code(), 42);
143+
assert_eq!(&*error.domain(), ns_string!("MyDomain"));
144+
assert_eq!(
145+
format!("{}", error),
146+
"The operation couldn’t be completed. (MyDomain error 42.)"
147+
);
148+
}
149+
150+
#[test]
151+
fn basic() {
152+
let error = NSError::new(-999, ns_string!("NSURLErrorDomain"));
153+
assert_eq!(
154+
format!("{}", error),
155+
"The operation couldn’t be completed. (NSURLErrorDomain error -999.)"
156+
);
157+
}
158+
}

objc2-foundation/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ pub use self::copying::{NSCopying, NSMutableCopying};
5555
pub use self::data::NSData;
5656
pub use self::dictionary::NSDictionary;
5757
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
58+
pub use self::error::{NSError, NSErrorDomain, NSErrorUserInfoKey};
5859
pub use self::exception::NSException;
5960
pub use self::geometry::{CGFloat, NSPoint, NSRect, NSSize};
6061
pub use self::mutable_array::NSMutableArray;
@@ -103,6 +104,7 @@ mod data;
103104
mod declare_macro;
104105
mod dictionary;
105106
mod enumerator;
107+
mod error;
106108
mod exception;
107109
mod geometry;
108110
mod macros;

0 commit comments

Comments
 (0)