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!(anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context(FfxError::TestingError), Err(Error::Unexpected(_)), "anyhow.bug_context() should create a bugcheck error even if given an ffx error (magic reduction)");
162 assert_matches!(
163 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
164 Err(Error::User(_)),
165 "anyhow.user_message() should be a user error"
166 );
167 assert_matches!(
168 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
169 Err(Error::User(_)),
170 "anyhow.with_user_message() should be a user error"
171 );
172 assert_matches!(anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| FfxError::TestingError).ffx_error(), Some(FfxError::TestingError), "anyhow.with_user_message should be a user error that properly extracts to the ffx error.");
173 }
174
175 #[test]
176 fn test_user_error_formats_through_multiple_levels() {
177 let user_err =
178 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
179 let user_err2 = user_err.user_message("getting wubbler");
180 let err_string = format!("{}", user_err2.unwrap_err());
181 assert_eq!(err_string, "getting wubbler: broken wubbler: the wubbler broke");
182 }
183
184 #[test]
185 fn test_bug_and_user_error_override_each_other_but_add_context() {
186 let user_err =
187 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
188 let user_err2 = user_err.bug_context("getting wubbler");
189 let user_err3 = user_err2.user_message("delegating wubbler");
190 let err_string = format!("{}", user_err3.unwrap_err());
191 assert_eq!(
192 err_string,
193 "delegating wubbler: getting wubbler: broken wubbler: the wubbler broke"
194 );
195 }
196
197 #[test]
198 fn test_bug_and_user_error_override_each_other_but_add_context_part_two() {
199 let user_err =
200 anyhow::Result::<()>::Err(anyhow!("the wubbler broke")).user_message("broken wubbler");
201 let user_err2 = user_err.bug_context("getting wubbler");
202 let user_err3 = user_err2.bug_context("delegating wubbler");
203 let err_string = format!("{}", user_err3.unwrap_err());
204 assert_eq!(
205 err_string,
206 "BUG: An internal command error occurred.\nError: delegating wubbler\n 1. getting wubbler\n 2. broken wubbler\n 3. the wubbler broke"
207 );
208 }
209}