Skip to content

Commit

Permalink
Add basic version of NSError
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Jul 27, 2022
1 parent 769ea10 commit e107f56
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 0 deletions.
1 change: 1 addition & 0 deletions objc2-foundation/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
`NSData` and `NSMutableData`.
* Implemented `Extend` for `NSMutableArray`.
* Add extra `Extend<&u8>` impl for `NSMutableData`.
* Added `NSError`.

### Changed
* Change selector syntax in `declare_class!` macro to be more Rust-like.
Expand Down
128 changes: 128 additions & 0 deletions objc2-foundation/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
use core::fmt;
use core::panic::{RefUnwindSafe, UnwindSafe};

use objc2::ffi::NSInteger;
use objc2::rc::{Id, Shared};
use objc2::{msg_send, msg_send_id};

use crate::{extern_class, NSCopying, NSDictionary, NSObject, NSString};

extern_class! {
/// Information about an error condition including a domain, a
/// domain-specific error code, and application-specific information.
///
/// See also [Apple's documentation][doc].
///
/// [doc]: https://developer.apple.com/documentation/foundation/nserror?language=objc
#[derive(PartialEq, Eq, Hash)]
unsafe pub struct NSError: NSObject;
}

// SAFETY: Error objects are immutable data containers.
unsafe impl Sync for NSError {}
unsafe impl Send for NSError {}

impl UnwindSafe for NSError {}
impl RefUnwindSafe for NSError {}

pub type NSErrorUserInfoKey = NSString;
pub type NSErrorDomain = NSString;

/// Creation methods.
impl NSError {
/// Construct a new [`NSError`] with the given code in the given domain.
pub fn new(code: NSInteger, domain: &NSString) -> Id<Self, Shared> {
unsafe { Self::with_user_info(code, domain, None) }
}

// TODO: Figure out safety of `user_info` dict!
unsafe fn with_user_info(
code: NSInteger,
domain: &NSString,
user_info: Option<&NSDictionary<NSErrorUserInfoKey, NSObject>>,
) -> Id<Self, Shared> {
// SAFETY: `domain` and `user_info` are copied to the error object, so
// even if the `&NSString` came from a `&mut NSMutableString`, we're
// still good!
unsafe {
msg_send_id![
msg_send_id![Self::class(), alloc],
initWithDomain: domain,
code: code,
userInfo: user_info,
]
.expect("unexpected NULL NSError")
}
}
}

/// Accessor methods.
impl NSError {
pub fn domain(&self) -> Id<NSString, Shared> {
unsafe { msg_send_id![self, domain].expect("unexpected NULL NSError domain") }
}

pub fn code(&self) -> NSInteger {
unsafe { msg_send![self, code] }
}

pub fn user_info(&self) -> Option<Id<NSDictionary<NSErrorUserInfoKey, NSObject>, Shared>> {
unsafe { msg_send_id![self, userInfo] }
}

pub fn localized_description(&self) -> Id<NSString, Shared> {
unsafe {
msg_send_id![self, localizedDescription].expect(
"unexpected NULL localized description; a default should have been generated!",
)
}
}

// TODO: localizedRecoveryOptions
// TODO: localizedRecoverySuggestion
// TODO: localizedFailureReason
// TODO: helpAnchor
// TODO: +setUserInfoValueProviderForDomain:provider:
// TODO: +userInfoValueProviderForDomain:

// TODO: recoveryAttempter
// TODO: attemptRecoveryFromError:...

// TODO: Figure out if this is a good design, or if we should do something
// differently (like a Rusty name for the function, or putting a bunch of
// statics in a module instead)?
#[allow(non_snake_case)]
pub fn NSLocalizedDescriptionKey() -> &'static NSErrorUserInfoKey {
extern "C" {
#[link_name = "NSLocalizedDescriptionKey"]
static VALUE: &'static NSErrorUserInfoKey;
}
unsafe { VALUE }
}

// TODO: Other NSErrorUserInfoKey values
// TODO: NSErrorDomain values
}

impl fmt::Debug for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("NSError")
.field("domain", &self.domain())
.field("code", &self.code())
.field("user_info", &self.user_info())
.finish()
}
}

impl fmt::Display for NSError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.localized_description())
}
}

impl std::error::Error for NSError {}

unsafe impl NSCopying for NSError {
type Ownership = Shared;
type Output = Self;
}
2 changes: 2 additions & 0 deletions objc2-foundation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub use self::copying::{NSCopying, NSMutableCopying};
pub use self::data::NSData;
pub use self::dictionary::NSDictionary;
pub use self::enumerator::{NSEnumerator, NSFastEnumeration, NSFastEnumerator};
pub use self::error::{NSError, NSErrorUserInfoKey, NSLocalizedDescriptionKey};
pub use self::exception::NSException;
pub use self::geometry::{CGFloat, NSPoint, NSRect, NSSize};
pub use self::mutable_array::NSMutableArray;
Expand Down Expand Up @@ -103,6 +104,7 @@ mod data;
mod declare_macro;
mod dictionary;
mod enumerator;
mod error;
mod exception;
mod geometry;
mod macros;
Expand Down

0 comments on commit e107f56

Please sign in to comment.