ffx_command_error/
context.rs1use crate::Error;
6use anyhow::Context;
7use errors::{FfxError, IntoExitCode, ResultExt};
8use std::fmt::Display;
9
10pub trait FfxContext<T, E> {
13 fn bug(self) -> Result<T, Error>;
16
17 fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
20
21 fn with_bug_context<C: Display + Send + Sync + 'static>(
24 self,
25 f: impl FnOnce() -> C,
26 ) -> Result<T, Error>;
27
28 fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
31
32 fn with_user_message<C: Display + Send + Sync + 'static>(
35 self,
36 f: impl FnOnce() -> C,
37 ) -> Result<T, Error>;
38}
39
40fn unwrap_source(err: anyhow::Error) -> anyhow::Error {
45 match err.downcast::<Error>() {
46 Ok(e) => match e.source() {
47 Ok(source) => source,
48 Err(e) => e.into(),
49 },
50 Err(e) => e,
51 }
52}
53
54impl<T, E> FfxContext<T, E> for Result<T, E>
55where
56 Self: anyhow::Context<T, E>,
57 E: Into<anyhow::Error>,
58{
59 fn bug(self) -> Result<T, Error> {
60 self.map_err(|e| Error::Unexpected(e.into()))
61 }
62
63 fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
64 self.map_err(|e| Error::Unexpected(unwrap_source(e.into()).context(context)))
65 }
66
67 fn with_bug_context<C: Display + Send + Sync + 'static>(
68 self,
69 f: impl FnOnce() -> C,
70 ) -> Result<T, Error> {
71 self.bug_context((f)())
72 }
73
74 fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
75 self.map_err(|e| Error::User(unwrap_source(e.into()).context(context)))
76 }
77
78 fn with_user_message<C: Display + Send + Sync + 'static>(
79 self,
80 f: impl FnOnce() -> C,
81 ) -> Result<T, Error> {
82 self.user_message((f)())
83 }
84}
85
86impl<T> FfxContext<T, core::convert::Infallible> for Option<T>
87where
88 Self: anyhow::Context<T, core::convert::Infallible>,
89{
90 fn bug(self) -> Result<T, Error> {
91 self.ok_or_else(|| Error::Unexpected(anyhow::anyhow!("Option is None")))
92 }
93
94 fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
95 self.context(context).map_err(Error::Unexpected)
96 }
97
98 fn with_bug_context<C: Display + Send + Sync + 'static>(
99 self,
100 f: impl FnOnce() -> C,
101 ) -> Result<T, Error> {
102 self.with_context(f).map_err(Error::Unexpected)
103 }
104
105 fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
106 self.context(context).map_err(Error::User)
107 }
108
109 fn with_user_message<C: Display + Send + Sync + 'static>(
110 self,
111 f: impl FnOnce() -> C,
112 ) -> Result<T, Error> {
113 self.with_context(f).map_err(Error::User)
114 }
115}
116
117impl ResultExt for Error {
118 fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
119 match self {
120 Error::User(err) => err.downcast_ref(),
121 _ => None,
122 }
123 }
124}
125impl IntoExitCode for Error {
126 fn exit_code(&self) -> i32 {
127 use Error::*;
128 match self {
129 Help { code, .. } | ExitWithCode(code) => *code,
130 IoError(_) => 1,
131 Unexpected(err) | User(err) | Config(err) => {
132 err.ffx_error().map(FfxError::exit_code).unwrap_or(1)
133 }
134 }
135 }
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::tests::*;
142 use anyhow::anyhow;
143 use assert_matches::assert_matches;
144
145 #[test]
146 fn error_context_helpers() {
147 assert_matches!(
148 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug(),
149 Err(Error::Unexpected(_)),
150 "anyhow.bug() should be a bugcheck error"
151 );
152 assert_matches!(
153 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context("boom"),
154 Err(Error::Unexpected(_)),
155 "anyhow.bug_context() should be a bugcheck error"
156 );
157 assert_matches!(
158 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_bug_context(|| "boom"),
159 Err(Error::Unexpected(_)),
160 "anyhow.bug_context() should be a bugcheck error"
161 );
162 assert_matches!(
163 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context(FfxError::TestingError),
164 Err(Error::Unexpected(_)),
165 "anyhow.bug_context() should create a bugcheck error even if given an ffx error (magic reduction)"
166 );
167 assert_matches!(
168 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
169 Err(Error::User(_)),
170 "anyhow.user_message() should be a user error"
171 );
172 assert_matches!(
173 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
174 Err(Error::User(_)),
175 "anyhow.with_user_message() should be a user error"
176 );
177 assert_matches!(
178 anyhow::Result::<()>::Err(anyhow!(ERR_STR))
179 .with_user_message(|| FfxError::TestingError)
180 .ffx_error(),
181 Some(FfxError::TestingError),
182 "anyhow.with_user_message should be a user error that properly extracts to the ffx error."
183 );
184 }
185
186 #[test]
187 fn test_user_error_formats_through_multiple_levels() {
188 let user_err =
189 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
190 let user_err2 = user_err.user_message("getting wubbler");
191 let err_string = format!("{}", user_err2.unwrap_err());
192 assert_eq!(err_string, "getting wubbler: broken wubbler: the wubbler broke");
193 }
194
195 #[test]
196 fn test_bug_and_user_error_override_each_other_but_add_context() {
197 let user_err =
198 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
199 let user_err2 = user_err.bug_context("getting wubbler");
200 let user_err3 = user_err2.user_message("delegating wubbler");
201 let err_string = format!("{}", user_err3.unwrap_err());
202 assert_eq!(
203 err_string,
204 "delegating wubbler: getting wubbler: broken wubbler: the wubbler broke"
205 );
206 }
207
208 #[test]
209 fn test_bug_and_user_error_override_each_other_but_add_context_part_two() {
210 let user_err =
211 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
212 let user_err2 = user_err.bug_context("getting wubbler");
213 let user_err3 = user_err2.bug_context("delegating wubbler");
214 let err_string = format!("{}", user_err3.unwrap_err());
215 assert_eq!(
216 err_string,
217 "BUG: An internal command error occurred.\nError: delegating wubbler\n 1. getting wubbler\n 2. broken wubbler\n 3. the wubbler broke"
218 );
219 }
220}