Skip to main content

security/
error.rs

1//! Errors returned by the `security-rs` bindings.
2
3use core::fmt;
4
5use apple_cf::CFError;
6
7use crate::ffi;
8
9/// Convenient result alias used throughout this crate.
10pub type Result<T, E = SecurityError> = std::result::Result<T, E>;
11
12/// Structured `OSStatus` error returned by `Security.framework`.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub struct StatusError {
15    /// API name that returned the status code.
16    pub operation: &'static str,
17    /// Raw `OSStatus` numeric code.
18    pub status: ffi::OSStatus,
19    /// Human-readable description when available.
20    pub message: String,
21}
22
23impl fmt::Display for StatusError {
24    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
25        write!(
26            f,
27            "{} failed with OSStatus {}: {}",
28            self.operation, self.status, self.message
29        )
30    }
31}
32
33impl std::error::Error for StatusError {}
34
35/// Top-level error type returned by this crate.
36#[derive(Debug, Clone, PartialEq, Eq)]
37#[non_exhaustive]
38pub enum SecurityError {
39    /// Invalid input crossed the FFI boundary.
40    InvalidArgument(String),
41    /// A requested keychain item was missing.
42    ItemNotFound(String),
43    /// A duplicate keychain item already existed.
44    DuplicateItem(String),
45    /// Authentication UI was suppressed or otherwise unavailable.
46    InteractionNotAllowed(String),
47    /// Trust evaluation failed and Security.framework provided a reason.
48    TrustEvaluationFailed(String),
49    /// A Core Foundation creation call returned a null pointer.
50    CoreFoundation(CFError),
51    /// Security.framework returned an unexpected Core Foundation type.
52    UnexpectedType {
53        /// API name being decoded.
54        operation: &'static str,
55        /// Expected Core Foundation family.
56        expected: &'static str,
57    },
58    /// Security.framework returned an `OSStatus` not covered by a more specific variant.
59    Status(StatusError),
60}
61
62impl SecurityError {
63    /// Numeric `OSStatus`, when this error originated from one.
64    #[must_use]
65    pub const fn code(&self) -> Option<ffi::OSStatus> {
66        match self {
67            Self::ItemNotFound(_) => Some(ffi::status::ITEM_NOT_FOUND),
68            Self::DuplicateItem(_) => Some(ffi::status::DUPLICATE_ITEM),
69            Self::InteractionNotAllowed(_) => Some(ffi::status::INTERACTION_NOT_ALLOWED),
70            Self::Status(error) => Some(error.status),
71            _ => None,
72        }
73    }
74
75    pub(crate) const fn from_status(
76        operation: &'static str,
77        status: ffi::OSStatus,
78        message: String,
79    ) -> Self {
80        match status {
81            ffi::status::ITEM_NOT_FOUND => Self::ItemNotFound(message),
82            ffi::status::DUPLICATE_ITEM => Self::DuplicateItem(message),
83            ffi::status::INTERACTION_NOT_ALLOWED => Self::InteractionNotAllowed(message),
84            _ => Self::Status(StatusError {
85                operation,
86                status,
87                message,
88            }),
89        }
90    }
91}
92
93impl fmt::Display for SecurityError {
94    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95        match self {
96            Self::InvalidArgument(message) => write!(f, "invalid argument: {message}"),
97            Self::ItemNotFound(message) => write!(f, "item not found: {message}"),
98            Self::DuplicateItem(message) => write!(f, "duplicate item: {message}"),
99            Self::InteractionNotAllowed(message) => write!(f, "interaction not allowed: {message}"),
100            Self::TrustEvaluationFailed(message) => write!(f, "trust evaluation failed: {message}"),
101            Self::CoreFoundation(error) => write!(f, "{error}"),
102            Self::UnexpectedType {
103                operation,
104                expected,
105            } => write!(
106                f,
107                "{operation} returned an unexpected Core Foundation type (expected {expected})"
108            ),
109            Self::Status(error) => write!(f, "{error}"),
110        }
111    }
112}
113
114impl std::error::Error for SecurityError {
115    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
116        match self {
117            Self::CoreFoundation(error) => Some(error),
118            Self::Status(error) => Some(error),
119            _ => None,
120        }
121    }
122}