1// Copyright 2023 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
45use crate::Error;
6use anyhow::Context;
7use errors::{FfxError, IntoExitCode, ResultExt};
8use std::fmt::Display;
910/// Adds helpers to result types to produce useful error messages to the user from
11/// the ffx frontend (through [`crate::Error`])
12pub trait FfxContext<T, E> {
13/// Make this error into a BUG check that will display to the user as an error that
14 /// shouldn't happen.
15fn bug(self) -> Result<T, Error>;
1617/// Make this error into a BUG check that will display to the user as an error that
18 /// shouldn't happen, with the added context.
19fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
2021/// Make this error into a BUG check that will display to the user as an error that
22 /// shouldn't happen, with the added context returned by the closure `f`.
23fn with_bug_context<C: Display + Send + Sync + 'static>(
24self,
25 f: impl FnOnce() -> C,
26 ) -> Result<T, Error>;
2728/// Make this error into a displayed user error, with the added context for display to the user.
29 /// Use this for errors that happen in the normal course of execution, like files not being found.
30fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
3132/// Make this error into a displayed user error, with the added context for display to the user.
33 /// Use this for errors that happen in the normal course of execution, like files not being found.
34fn with_user_message<C: Display + Send + Sync + 'static>(
35self,
36 f: impl FnOnce() -> C,
37 ) -> Result<T, Error>;
38}
3940impl<T, E> FfxContext<T, E> for Result<T, E>
41where
42Self: anyhow::Context<T, E>,
43 E: Into<anyhow::Error>,
44{
45fn bug(self) -> Result<T, Error> {
46self.map_err(|e| Error::Unexpected(e.into()))
47 }
4849fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
50self.context(context).map_err(Error::Unexpected)
51 }
5253fn with_bug_context<C: Display + Send + Sync + 'static>(
54self,
55 f: impl FnOnce() -> C,
56 ) -> Result<T, Error> {
57self.with_context(f).map_err(Error::Unexpected)
58 }
5960fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
61self.context(context).map_err(Error::User)
62 }
6364fn with_user_message<C: Display + Send + Sync + 'static>(
65self,
66 f: impl FnOnce() -> C,
67 ) -> Result<T, Error> {
68self.with_context(f).map_err(Error::User)
69 }
70}
7172impl<T> FfxContext<T, core::convert::Infallible> for Option<T>
73where
74Self: anyhow::Context<T, core::convert::Infallible>,
75{
76fn bug(self) -> Result<T, Error> {
77self.ok_or_else(|| Error::Unexpected(anyhow::anyhow!("Option is None")))
78 }
7980fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
81self.context(context).map_err(Error::Unexpected)
82 }
8384fn with_bug_context<C: Display + Send + Sync + 'static>(
85self,
86 f: impl FnOnce() -> C,
87 ) -> Result<T, Error> {
88self.with_context(f).map_err(Error::Unexpected)
89 }
9091fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
92self.context(context).map_err(Error::User)
93 }
9495fn with_user_message<C: Display + Send + Sync + 'static>(
96self,
97 f: impl FnOnce() -> C,
98 ) -> Result<T, Error> {
99self.with_context(f).map_err(Error::User)
100 }
101}
102103impl ResultExt for Error {
104fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
105match self {
106 Error::User(err) => err.downcast_ref(),
107_ => None,
108 }
109 }
110}
111impl IntoExitCode for Error {
112fn exit_code(&self) -> i32 {
113use Error::*;
114match 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}
122123#[cfg(test)]
124mod tests {
125use super::*;
126use crate::tests::*;
127use anyhow::anyhow;
128use assert_matches::assert_matches;
129130#[test]
131fn error_context_helpers() {
132assert_matches!(
133 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug(),
134Err(Error::Unexpected(_)),
135"anyhow.bug() should be a bugcheck error"
136);
137assert_matches!(
138 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context("boom"),
139Err(Error::Unexpected(_)),
140"anyhow.bug_context() should be a bugcheck error"
141);
142assert_matches!(
143 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_bug_context(|| "boom"),
144Err(Error::Unexpected(_)),
145"anyhow.bug_context() should be a bugcheck error"
146);
147assert_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)");
148assert_matches!(
149 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
150Err(Error::User(_)),
151"anyhow.user_message() should be a user error"
152);
153assert_matches!(
154 anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
155Err(Error::User(_)),
156"anyhow.with_user_message() should be a user error"
157);
158assert_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}