From e107f568735b74e7eec1355f2f0a12945bf06034 Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 10 Jan 2022 23:31:51 +0100 Subject: [PATCH] Add basic version of NSError Inspiration from: - https://github.com/SSheldon/rust-objc-foundation/pull/16 - https://github.com/nvzqz/fruity/tree/v0.3.0/src/foundation/ns_error - https://github.com/ryanmcgrath/cacao/blob/02946476837e45ee381e37dde2946ed63b44dc31/src/error.rs --- objc2-foundation/CHANGELOG.md | 1 + objc2-foundation/src/error.rs | 128 ++++++++++++++++++++++++++++++++++ objc2-foundation/src/lib.rs | 2 + 3 files changed, 131 insertions(+) create mode 100644 objc2-foundation/src/error.rs diff --git a/objc2-foundation/CHANGELOG.md b/objc2-foundation/CHANGELOG.md index 6b82ad3cd..542839771 100644 --- a/objc2-foundation/CHANGELOG.md +++ b/objc2-foundation/CHANGELOG.md @@ -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. diff --git a/objc2-foundation/src/error.rs b/objc2-foundation/src/error.rs new file mode 100644 index 000000000..d085d10a5 --- /dev/null +++ b/objc2-foundation/src/error.rs @@ -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 { + 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>, + ) -> Id { + // 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 { + 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, Shared>> { + unsafe { msg_send_id![self, userInfo] } + } + + pub fn localized_description(&self) -> Id { + 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; +} diff --git a/objc2-foundation/src/lib.rs b/objc2-foundation/src/lib.rs index 392ecc818..9ef1c4090 100644 --- a/objc2-foundation/src/lib.rs +++ b/objc2-foundation/src/lib.rs @@ -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; @@ -103,6 +104,7 @@ mod data; mod declare_macro; mod dictionary; mod enumerator; +mod error; mod exception; mod geometry; mod macros;