Skip to main content

computer_use/
error.rs

1use std::fmt;
2
3use crate::app::AppInfo;
4use crate::state::WindowInfo;
5
6/// Convenience alias used throughout the crate.
7pub type Result<T, E = Error> = std::result::Result<T, E>;
8
9/// An OS permission that computer use depends on.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11#[non_exhaustive]
12pub enum Permission {
13    /// Reading and driving other apps through the accessibility API.
14    Accessibility,
15    /// Capturing pixels of other apps' windows.
16    ScreenRecording,
17}
18
19impl fmt::Display for Permission {
20    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
21        f.write_str(match self {
22            Self::Accessibility => "accessibility",
23            Self::ScreenRecording => "screen recording",
24        })
25    }
26}
27
28/// Everything that can go wrong while observing or driving an app.
29#[derive(Debug, thiserror::Error)]
30#[non_exhaustive]
31pub enum Error {
32    /// No running or installed app matched the query.
33    #[error("app not found: {0}")]
34    AppNotFound(String),
35
36    /// Several running apps matched the query equally well.
37    #[error("{query:?} matches more than one running app: {}", list(candidates, |a| format!("{} (pid {})", a.name, a.pid.unwrap_or_default())))]
38    AmbiguousApp {
39        /// The query.
40        query: String,
41        /// The matching apps.
42        candidates: Vec<AppInfo>,
43    },
44
45    /// Several of an app's windows matched a title.
46    #[error("{title:?} matches more than one window: {}", list(candidates, |w| format!("{:?} (id {})", w.title.as_deref().unwrap_or_default(), w.id.unwrap_or_default())))]
47    AmbiguousWindow {
48        /// The title.
49        title: String,
50        /// The matching windows.
51        candidates: Vec<WindowInfo>,
52    },
53
54    /// The app is running but has no window that can be observed.
55    #[error("no usable window for app {0}")]
56    WindowNotFound(String),
57
58    /// An action referenced app state that was never captured (or was discarded).
59    #[error("no app state for {0}; call get_app_state first")]
60    NoAppState(String),
61
62    /// An element index did not exist in the latest captured state.
63    #[error("element {index} not found in the latest state of {app}")]
64    ElementNotFound {
65        /// App the lookup was made against.
66        app: String,
67        /// The requested element index.
68        index: usize,
69    },
70
71    /// A caller-supplied argument was invalid.
72    #[error("invalid argument: {0}")]
73    InvalidArgument(String),
74
75    /// A required OS permission has not been granted to this process.
76    #[error("{0} permission has not been granted to this process")]
77    PermissionDenied(Permission),
78
79    /// The operation is not available on this platform or backend.
80    #[error("unsupported: {0}")]
81    Unsupported(String),
82
83    /// The platform accepted the request but reported that it failed.
84    #[error("action failed: {0}")]
85    ActionFailed(String),
86
87    /// The state the action needs is missing (for example, no screenshot was captured).
88    #[error("state unavailable: {0}")]
89    StateUnavailable(String),
90
91    /// A platform API returned an unexpected error.
92    #[error("platform error: {0}")]
93    Platform(String),
94
95    /// Encoding a screenshot failed.
96    #[error("image encoding failed: {0}")]
97    Image(#[from] image::ImageError),
98
99    /// An I/O error, for example while launching an app.
100    #[error(transparent)]
101    Io(#[from] std::io::Error),
102}
103
104impl Error {
105    pub(crate) fn invalid(message: impl Into<String>) -> Self {
106        Self::InvalidArgument(message.into())
107    }
108
109    #[cfg_attr(not(target_os = "macos"), allow(dead_code))]
110    pub(crate) fn platform(message: impl Into<String>) -> Self {
111        Self::Platform(message.into())
112    }
113
114    pub(crate) fn action(message: impl Into<String>) -> Self {
115        Self::ActionFailed(message.into())
116    }
117}
118
119fn list<T>(items: &[T], describe: impl Fn(&T) -> String) -> String {
120    items.iter().map(describe).collect::<Vec<_>>().join(", ")
121}