ffx_command_error/
context.rsuse crate::Error;
use anyhow::Context;
use errors::{FfxError, IntoExitCode, ResultExt};
use std::fmt::Display;
pub trait FfxContext<T, E> {
fn bug(self) -> Result<T, Error>;
fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
fn with_bug_context<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error>;
fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error>;
fn with_user_message<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error>;
}
impl<T, E> FfxContext<T, E> for Result<T, E>
where
Self: anyhow::Context<T, E>,
E: Into<anyhow::Error>,
{
fn bug(self) -> Result<T, Error> {
self.map_err(|e| Error::Unexpected(e.into()))
}
fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
self.context(context).map_err(Error::Unexpected)
}
fn with_bug_context<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error> {
self.with_context(f).map_err(Error::Unexpected)
}
fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
self.context(context).map_err(Error::User)
}
fn with_user_message<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error> {
self.with_context(f).map_err(Error::User)
}
}
impl<T> FfxContext<T, core::convert::Infallible> for Option<T>
where
Self: anyhow::Context<T, core::convert::Infallible>,
{
fn bug(self) -> Result<T, Error> {
self.ok_or_else(|| Error::Unexpected(anyhow::anyhow!("Option is None")))
}
fn bug_context<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
self.context(context).map_err(Error::Unexpected)
}
fn with_bug_context<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error> {
self.with_context(f).map_err(Error::Unexpected)
}
fn user_message<C: Display + Send + Sync + 'static>(self, context: C) -> Result<T, Error> {
self.context(context).map_err(Error::User)
}
fn with_user_message<C: Display + Send + Sync + 'static>(
self,
f: impl FnOnce() -> C,
) -> Result<T, Error> {
self.with_context(f).map_err(Error::User)
}
}
impl ResultExt for Error {
fn ffx_error<'a>(&'a self) -> Option<&'a FfxError> {
match self {
Error::User(err) => err.downcast_ref(),
_ => None,
}
}
}
impl IntoExitCode for Error {
fn exit_code(&self) -> i32 {
use Error::*;
match self {
Help { code, .. } | ExitWithCode(code) => *code,
Unexpected(err) | User(err) | Config(err) => {
err.ffx_error().map(FfxError::exit_code).unwrap_or(1)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::*;
use anyhow::anyhow;
use assert_matches::assert_matches;
#[test]
fn error_context_helpers() {
assert_matches!(
anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug(),
Err(Error::Unexpected(_)),
"anyhow.bug() should be a bugcheck error"
);
assert_matches!(
anyhow::Result::<()>::Err(anyhow!(ERR_STR)).bug_context("boom"),
Err(Error::Unexpected(_)),
"anyhow.bug_context() should be a bugcheck error"
);
assert_matches!(
anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_bug_context(|| "boom"),
Err(Error::Unexpected(_)),
"anyhow.bug_context() should be a bugcheck error"
);
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)");
assert_matches!(
anyhow::Result::<()>::Err(anyhow!(ERR_STR)).user_message("boom"),
Err(Error::User(_)),
"anyhow.user_message() should be a user error"
);
assert_matches!(
anyhow::Result::<()>::Err(anyhow!(ERR_STR)).with_user_message(|| "boom"),
Err(Error::User(_)),
"anyhow.with_user_message() should be a user error"
);
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.");
}
}