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
40impl<T, E> FfxContext<T, E> for Result<T, E>
41where
42 Self: anyhow::Context<T, E>,
43 E: Into<anyhow::Error>,
44{
45 fn bug(self) -> Result<T, Error> {
46 self.map_err(|e| Error::Unexpected(e.into()))
47 }
48
49 fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
50 self.context(context).map_err(Error::Unexpected)
51 }
52
53 fn with_bug_context<C: Display + Send + Sync + 'static>(
54 self,
55 f: impl FnOnce() -> C,
56 ) -> Result<T, Error> {
57 self.with_context(f).map_err(Error::Unexpected)
58 }
59
60 fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
61 self.context(context).map_err(Error::User)
62 }
63
64 fn with_user_message<C: Display + Send + Sync + 'static>(
65 self,
66 f: impl FnOnce() -> C,
67 ) -> Result<T, Error> {
68 self.with_context(f).map_err(Error::User)
69 }
70}
71
72impl<T> FfxContext<T, core::convert::Infallible> for Option<T>
73where
74 Self: anyhow::Context<T, core::convert::Infallible>,
75{
76 fn bug(self) -> Result<T, Error> {
77 self.ok_or_else(|| Error::Unexpected(anyhow::anyhow!("Option is None")))
78 }
79
80 fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
81 self.context(context).map_err(Error::Unexpected)
82 }
83
84 fn with_bug_context<C: Display + Send + Sync + 'static>(
85 self,
86 f: impl FnOnce() -> C,
87 ) -> Result<T, Error> {
88 self.with_context(f).map_err(Error::Unexpected)
89 }
90
91 fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
92 self.context(context).map_err(Error::User)
93 }
94
95 fn with_user_message<C: Display + Send + Sync + 'static>(
96 self,
97 f: impl FnOnce() -> C,
98 ) -> Result<T, Error> {
99 self.with_context(f).map_err(Error::User)
100 }
101}
102
103impl ResultExt for Error {
104 fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
105 match self {
106 Error::User(err) => err.downcast_ref(),
107 _ => None,
108 }
109 }
110}
111impl IntoExitCode for Error {
112 fn exit_code(&self) -> i32 {
113 use Error::*;
114 match self {
115 Help { code, .. } | ExitWithCode(code) => *code,
116 Unexpected(err) | User(err) | Config(err) => {
117 err.ffx_error().map(FfxError::exit_code).unwrap_or(1)
118 }
119 }
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126 use crate::tests::*;
127 use anyhow::anyhow;
128 use assert_matches::assert_matches;
129
130 #[test]
131 fn error_context_helpers() {
132 assert_matches!(
133 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug(),
134 Err(Error::Unexpected(_)),
135 "anyhow.bug() should be a bugcheck error"
136 );
137 assert_matches!(
138 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context("boom"),
139 Err(Error::Unexpected(_)),
140 "anyhow.bug_context() should be a bugcheck error"
141 );
142 assert_matches!(
143 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_bug_context(|| "boom"),
144 Err(Error::Unexpected(_)),
145 "anyhow.bug_context() should be a bugcheck error"
146 );
147 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)");
148 assert_matches!(
149 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
150 Err(Error::User(_)),
151 "anyhow.user_message() should be a user error"
152 );
153 assert_matches!(
154 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
155 Err(Error::User(_)),
156 "anyhow.with_user_message() should be a user error"
157 );
158 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.");
159 }
160}