Skip to main content

ferroui_base/input/platform/
clipboard_error.rs

1use std::error::Error;
2use std::fmt;
3
4/// The category of a failed clipboard operation.
5///
6/// The reference reports a failed clipboard operation with an exception
7/// whose type tells what happened; the kinds mirror the exception types the
8/// platform clipboards raise:
9///
10/// | Kind | Reference exception |
11/// |---|---|
12/// | [`Timeout`](Self::Timeout) | `TimeoutException` |
13/// | [`Canceled`](Self::Canceled) | `OperationCanceledException` (and `TaskCanceledException`) |
14/// | [`AccessDenied`](Self::AccessDenied) | `UnauthorizedAccessException` |
15/// | [`Platform`](Self::Platform) | `COMException` (an `ExternalException` carrying a result code) |
16/// | [`Other`](Self::Other) | every other exception |
17#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
18pub enum ClipboardErrorKind {
19    /// The clipboard could not be opened or did not answer in time, usually
20    /// because another application holds it.
21    Timeout,
22    /// The operation was canceled before it completed.
23    Canceled,
24    /// The platform denied access to the clipboard.
25    AccessDenied,
26    /// A call of the platform clipboard interface failed with a result
27    /// code (see [`ClipboardError::code`]).
28    Platform,
29    /// Any other failure: one a platform clipboard is not expected to
30    /// report.
31    Other,
32}
33
34impl ClipboardErrorKind {
35    fn description(self) -> &'static str {
36        match self {
37            ClipboardErrorKind::Timeout => "The clipboard operation has timed out.",
38            ClipboardErrorKind::Canceled => "The clipboard operation was canceled.",
39            ClipboardErrorKind::AccessDenied => "Access to the clipboard was denied.",
40            ClipboardErrorKind::Platform => "A platform clipboard call failed.",
41            ClipboardErrorKind::Other => "The clipboard operation failed.",
42        }
43    }
44}
45
46/// The failure of a clipboard operation: what a platform clipboard reports
47/// instead of a result.
48#[derive(Clone, Debug, PartialEq, Eq)]
49pub struct ClipboardError {
50    kind: ClipboardErrorKind,
51    message: String,
52    code: Option<i32>,
53}
54
55impl ClipboardError {
56    /// Creates an error of a kind with a message.
57    pub fn new(kind: ClipboardErrorKind, message: impl Into<String>) -> ClipboardError {
58        ClipboardError { kind, message: message.into(), code: None }
59    }
60
61    /// Creates an error of a kind with the default message of the kind.
62    pub fn from_kind(kind: ClipboardErrorKind) -> ClipboardError {
63        ClipboardError::new(kind, kind.description())
64    }
65
66    /// The clipboard could not be opened or did not answer in time.
67    pub fn timeout(message: impl Into<String>) -> ClipboardError {
68        ClipboardError::new(ClipboardErrorKind::Timeout, message)
69    }
70
71    /// The operation was canceled.
72    pub fn canceled(message: impl Into<String>) -> ClipboardError {
73        ClipboardError::new(ClipboardErrorKind::Canceled, message)
74    }
75
76    /// The platform denied access to the clipboard.
77    pub fn access_denied(message: impl Into<String>) -> ClipboardError {
78        ClipboardError::new(ClipboardErrorKind::AccessDenied, message)
79    }
80
81    /// A call of the platform clipboard interface failed with the result
82    /// code `code`.
83    pub fn platform(code: i32, message: impl Into<String>) -> ClipboardError {
84        ClipboardError { kind: ClipboardErrorKind::Platform, message: message.into(), code: Some(code) }
85    }
86
87    /// A failure a platform clipboard is not expected to report.
88    pub fn other(message: impl Into<String>) -> ClipboardError {
89        ClipboardError::new(ClipboardErrorKind::Other, message)
90    }
91
92    /// The category of the failure.
93    pub fn kind(&self) -> ClipboardErrorKind {
94        self.kind
95    }
96
97    /// The message describing the failure.
98    pub fn message(&self) -> &str {
99        &self.message
100    }
101
102    /// The result code of the platform call that failed, for
103    /// [`ClipboardErrorKind::Platform`] errors.
104    pub fn code(&self) -> Option<i32> {
105        self.code
106    }
107}
108
109impl From<ClipboardErrorKind> for ClipboardError {
110    fn from(kind: ClipboardErrorKind) -> ClipboardError {
111        ClipboardError::from_kind(kind)
112    }
113}
114
115impl fmt::Display for ClipboardError {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        f.write_str(&self.message)?;
118        if let Some(code) = self.code {
119            write!(f, " (0x{:08X})", code as u32)?;
120        }
121        Ok(())
122    }
123}
124
125impl Error for ClipboardError {}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn constructors_set_the_kind_the_message_and_the_code() {
133        let cases = [
134            (ClipboardError::timeout("a"), ClipboardErrorKind::Timeout),
135            (ClipboardError::canceled("a"), ClipboardErrorKind::Canceled),
136            (ClipboardError::access_denied("a"), ClipboardErrorKind::AccessDenied),
137            (ClipboardError::other("a"), ClipboardErrorKind::Other),
138        ];
139        for (error, kind) in cases {
140            assert_eq!(kind, error.kind());
141            assert_eq!("a", error.message());
142            assert_eq!(None, error.code());
143            assert_eq!("a", error.to_string());
144        }
145
146        let error = ClipboardError::platform(0x8000_4005_u32 as i32, "Native call failed");
147        assert_eq!(ClipboardErrorKind::Platform, error.kind());
148        assert_eq!(Some(0x8000_4005_u32 as i32), error.code());
149        assert_eq!("Native call failed (0x80004005)", error.to_string());
150    }
151
152    #[test]
153    fn an_error_of_a_kind_has_the_message_of_the_kind() {
154        let error: ClipboardError = ClipboardErrorKind::Timeout.into();
155        assert_eq!(ClipboardErrorKind::Timeout, error.kind());
156        assert_eq!("The clipboard operation has timed out.", error.message());
157        assert_eq!(error, ClipboardError::from_kind(ClipboardErrorKind::Timeout));
158        assert_ne!(error, ClipboardError::from_kind(ClipboardErrorKind::Canceled));
159
160        let error: &dyn Error = &error;
161        assert!(error.source().is_none());
162    }
163}