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) | Error::Unexpected(err) | Error::Config(err) => err.ffx_error(),
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) => err.exit_code(),
132 }
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use crate::tests::*;
140 use anyhow::anyhow;
141 use assert_matches::assert_matches;
142
143 #[test]
144 fn error_context_helpers() {
145 assert_matches!(
146 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug(),
147 Err(Error::Unexpected(_)),
148 "anyhow.bug() should be a bugcheck error"
149 );
150 assert_matches!(
151 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context("boom"),
152 Err(Error::Unexpected(_)),
153 "anyhow.bug_context() should be a bugcheck error"
154 );
155 assert_matches!(
156 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_bug_context(|| "boom"),
157 Err(Error::Unexpected(_)),
158 "anyhow.bug_context() should be a bugcheck error"
159 );
160 assert_matches!(
161 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context(FfxError::TestingError),
162 Err(Error::Unexpected(_)),
163 "anyhow.bug_context() should create a bugcheck error even if given an ffx error (magic reduction)"
164 );
165 assert_matches!(
166 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
167 Err(Error::User(_)),
168 "anyhow.user_message() should be a user error"
169 );
170 assert_matches!(
171 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
172 Err(Error::User(_)),
173 "anyhow.with_user_message() should be a user error"
174 );
175 assert_matches!(
176 anyhow::Result::<()>::Err(anyhow!(ERR_STR))
177 .with_user_message(|| FfxError::TestingError)
178 .ffx_error(),
179 Some(FfxError::TestingError),
180 "anyhow.with_user_message should be a user error that properly extracts to the ffx error."
181 );
182 }
183
184 #[test]
185 fn test_user_error_formats_through_multiple_levels() {
186 let user_err =
187 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
188 let user_err2 = user_err.user_message("getting wubbler");
189 let err_string = format!("{}", user_err2.unwrap_err());
190 assert_eq!(err_string, "getting wubbler: broken wubbler: the wubbler broke");
191 }
192
193 #[test]
194 fn test_bug_and_user_error_override_each_other_but_add_context() {
195 let user_err =
196 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
197 let user_err2 = user_err.bug_context("getting wubbler");
198 let user_err3 = user_err2.user_message("delegating wubbler");
199 let err_string = format!("{}", user_err3.unwrap_err());
200 assert_eq!(
201 err_string,
202 "delegating wubbler: getting wubbler: broken wubbler: the wubbler broke"
203 );
204 }
205
206 #[test]
207 fn test_bug_and_user_error_override_each_other_but_add_context_part_two() {
208 let user_err =
209 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
210 let user_err2 = user_err.bug_context("getting wubbler");
211 let user_err3 = user_err2.bug_context("delegating wubbler");
212 let err_string = format!("{}", user_err3.unwrap_err());
213 assert_eq!(
214 err_string,
215 "BUG: An internal command error occurred.\nError: delegating wubbler\n 1. getting wubbler\n 2. broken wubbler\n 3. the wubbler broke"
216 );
217 }
218}