1use std::process::ExitStatus;
6
7#[doc(hidden)]
9pub mod macro_deps {
10 pub use anyhow;
11}
12
13pub const BUG_REPORT_URL: &str =
14 "https://issues.fuchsia.dev/issues/new?component=1378294&template=1838957";
15
16#[derive(thiserror::Error, Debug)]
23pub enum FfxError {
24 #[error("{}", .0)]
25 Error(#[source] anyhow::Error, i32 ),
26 #[error("Test Error")]
27 TestingError, #[cfg(not(target_os = "fuchsia"))]
29 #[error("{}", .err)]
30 DaemonError { err: Box<dyn std::error::Error + Send + Sync>, target: Option<String> },
31 #[cfg(not(target_os = "fuchsia"))]
32 #[error("{}", .err)]
33 OpenTargetError {
34 #[source]
35 err: Box<dyn std::error::Error + Send + Sync>,
36 target: Option<String>,
37 exit_code: i32,
38 },
39 #[cfg(not(target_os = "fuchsia"))]
40 #[error("{}", .err)]
41 TunnelError {
42 #[source]
43 err: Box<dyn std::error::Error + Send + Sync>,
44 target: Option<String>,
45 },
46 #[cfg(not(target_os = "fuchsia"))]
47 #[error("{}", .err)]
48 TargetConnectionError {
49 #[source]
50 err: Box<dyn std::error::Error + Send + Sync>,
51 target: Option<String>,
52 logs: Option<String>,
53 },
54}
55
56#[macro_export]
58macro_rules! ffx_error {
59 ($error_message: expr) => {{
60 $crate::FfxError::Error($crate::macro_deps::anyhow::anyhow!($error_message), 1)
61 }};
62 ($fmt:expr, $($arg:tt)*) => {
63 $crate::ffx_error!(format!($fmt, $($arg)*));
64 };
65}
66
67#[macro_export]
68macro_rules! ffx_error_with_code {
69 ($error_code:expr, $error_message:expr $(,)?) => {{
70 $crate::FfxError::Error($crate::macro_deps::anyhow::anyhow!($error_message), $error_code)
71 }};
72 ($error_code:expr, $fmt:expr, $($arg:tt)*) => {
73 $crate::ffx_error_with_code!($error_code, format!($fmt, $($arg)*));
74 };
75}
76
77#[macro_export]
78macro_rules! ffx_bail {
79 ($msg:literal $(,)?) => {
80 return Err($crate::ffx_error!($msg).into())
81 };
82 ($fmt:expr, $($arg:tt)*) => {
83 return Err($crate::ffx_error!($fmt, $($arg)*).into());
84 };
85}
86
87#[macro_export]
88macro_rules! ffx_bail_with_code {
89 ($code:literal, $msg:literal $(,)?) => {
90 return Err($crate::ffx_error_with_code!($code, $msg).into())
91 };
92 ($code:expr, $fmt:expr, $($arg:tt)*) => {
93 return Err($crate::ffx_error_with_code!($code, $fmt, $($arg)*).into());
94 };
95}
96
97pub trait IntoExitCode {
98 fn exit_code(&self) -> i32;
99}
100
101pub trait ResultExt: IntoExitCode {
102 fn ffx_error<'a>(&'a self) -> Option<&'a FfxError>;
103}
104
105impl ResultExt for anyhow::Error {
106 fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
107 self.downcast_ref()
108 }
109}
110
111impl IntoExitCode for anyhow::Error {
112 fn exit_code(&self) -> i32 {
113 match self.downcast_ref() {
114 Some(FfxError::Error(_, code)) => *code,
115 _ => 1,
116 }
117 }
118}
119
120impl IntoExitCode for FfxError {
121 fn exit_code(&self) -> i32 {
122 match self {
123 FfxError::Error(_, code) => *code,
124 FfxError::TestingError => 254,
125 #[cfg(not(target_os = "fuchsia"))]
126 FfxError::DaemonError { .. } => 1,
127 #[cfg(not(target_os = "fuchsia"))]
128 FfxError::OpenTargetError { exit_code, .. } => *exit_code,
129 #[cfg(not(target_os = "fuchsia"))]
130 FfxError::TunnelError { .. } => 1,
131 #[cfg(not(target_os = "fuchsia"))]
132 FfxError::TargetConnectionError { .. } => 1,
133 }
134 }
135}
136
137impl IntoExitCode for () {
139 fn exit_code(&self) -> i32 {
140 0
141 }
142}
143
144impl IntoExitCode for ExitStatus {
145 fn exit_code(&self) -> i32 {
146 self.code().unwrap_or(0)
147 }
148}
149
150impl<T, E> ResultExt for Result<T, E>
151where
152 T: IntoExitCode,
153 E: ResultExt,
154{
155 fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
156 match self {
157 Ok(_) => None,
158 Err(ref err) => err.ffx_error(),
159 }
160 }
161}
162
163impl<T, E> IntoExitCode for Result<T, E>
164where
165 T: IntoExitCode,
166 E: ResultExt,
167{
168 fn exit_code(&self) -> i32 {
169 match self {
170 Ok(code) => code.exit_code(),
171 Err(err) => err.exit_code(),
172 }
173 }
174}
175
176#[cfg(test)]
177mod test {
178 use super::*;
179 use anyhow::anyhow;
180 use assert_matches::assert_matches;
181
182 const FFX_STR: &str = "I am an ffx error";
183 const ERR_STR: &str = "I am not an ffx error";
184
185 #[test]
186 fn test_ffx_result_extension() {
187 let err = anyhow::Result::<()>::Err(anyhow!(ERR_STR));
188 assert!(err.ffx_error().is_none());
189
190 let err = anyhow::Result::<()>::Err(anyhow::Error::new(ffx_error!(FFX_STR)));
191 assert_matches!(err.ffx_error(), Some(FfxError::Error(_, _)));
192 }
193
194 #[test]
195 fn test_result_ext_exit_code_arbitrary_error() {
196 let err = Result::<(), _>::Err(anyhow!(ERR_STR));
197 assert_eq!(err.exit_code(), 1);
198 }
199}