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