Skip to content

File tree

4 files changed

+169
-4
lines changed

4 files changed

+169
-4
lines changed

objc2/CHANGELOG_FOUNDATION.md

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

1819
### Changed
1920
* **BREAKING**: Moved from external crate `objc2_foundation` into

objc2/src/foundation/error.rs

+162
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
use core::fmt;
2+
use core::panic::{RefUnwindSafe, UnwindSafe};
3+
4+
use super::{NSCopying, NSDictionary, NSObject, NSString};
5+
use crate::extern_class;
6+
use crate::ffi::NSInteger;
7+
use crate::rc::{Id, Shared};
8+
use crate::{msg_send, msg_send_id};
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+
let expected = if cfg!(feature = "apple") {
145+
"The operation couldn’t be completed. (MyDomain error 42.)"
146+
} else {
147+
"MyDomain 42"
148+
};
149+
assert_eq!(format!("{}", error), expected);
150+
}
151+
152+
#[test]
153+
fn basic() {
154+
let error = NSError::new(-999, ns_string!("NSURLErrorDomain"));
155+
let expected = if cfg!(feature = "apple") {
156+
"The operation couldn’t be completed. (NSURLErrorDomain error -999.)"
157+
} else {
158+
"NSURLErrorDomain -999"
159+
};
160+
assert_eq!(format!("{}", error), expected);
161+
}
162+
}

objc2/src/foundation/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ pub use self::copying::{NSCopying, NSMutableCopying};
3333
pub use self::data::NSData;
3434
pub use self::dictionary::NSDictionary;
3535
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
36+
pub use self::error::{NSError, NSErrorDomain, NSErrorUserInfoKey};
3637
pub use self::exception::NSException;
3738
pub use self::geometry::{CGFloat, NSPoint, NSRect, NSSize};
3839
pub use self::mutable_array::NSMutableArray;
@@ -71,6 +72,7 @@ mod copying;
7172
mod data;
7273
mod dictionary;
7374
mod enumerator;
75+
mod error;
7476
mod exception;
7577
mod geometry;
7678
mod mutable_array;

test-ui/ui/msg_send_id_invalid_return.stderr

+4-4
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied
2222
NSAttributedString
2323
NSData
2424
NSDictionary<K, V>
25+
NSError
2526
NSException
2627
NSMutableArray<T, O>
27-
NSMutableAttributedString
28-
and 9 others
28+
and 10 others
2929
= note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id<objc2::runtime::Class, Shared>>` for `RetainSemantics<true, false, false, false>`
3030
= note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info)
3131

@@ -50,10 +50,10 @@ error[E0277]: the trait bound `objc2::runtime::Class: Message` is not satisfied
5050
NSAttributedString
5151
NSData
5252
NSDictionary<K, V>
53+
NSError
5354
NSException
5455
NSMutableArray<T, O>
55-
NSMutableAttributedString
56-
and 9 others
56+
and 10 others
5757
= note: required because of the requirements on the impl of `MsgSendId<&objc2::runtime::Class, Id<Allocated<objc2::runtime::Class>, Shared>>` for `RetainSemantics<false, true, false, false>`
5858
= note: this error originates in the macro `msg_send_id` (in Nightly builds, run with -Z macro-backtrace for more info)
5959

0 commit comments

Comments
 (0)