errors/
lib.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use std::process::ExitStatus;
6
7/// Re-exported libraries for macros
8#[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/// The ffx main function expects a anyhow::Result from ffx plugins. If the Result is an Err it be
17/// downcast to FfxError, and if successful this error is presented as a user-readable error. All
18/// other error types are printed with full context and a BUG prefix, guiding the user to file bugs
19/// to improve the error condition that they have experienced, with a goal to maximize actionable
20/// errors over time.
21// TODO(https://fxbug.dev/42135455): consider extending this to allow custom types from plugins.
22#[derive(thiserror::Error, Debug)]
23pub enum FfxError {
24    #[error("{}", .0)]
25    Error(#[source] anyhow::Error, i32 /* Error status code */),
26    #[error("Test Error")]
27    TestingError, // this is here to be used in tests for verifying errors are translated properly.
28    #[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// Utility macro for constructing a FfxError::Error with a simple error string.
57#[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
137// so that Result<(), E>::Ok is treated as exit code 0.
138impl 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}