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