1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-24 07:53:00 +01:00

Added improved failure interoperability with downcasting (#285)

Deprecates Error::cause and introduces failure interoperability functions and downcasting.
This commit is contained in:
Armin Ronacher 2018-06-07 18:53:27 +02:00 committed by GitHub
parent 2d0b609c68
commit 789af0bbf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 105 additions and 7 deletions

View File

@ -34,21 +34,42 @@ use httpresponse::HttpResponse;
/// `Result`.
pub type Result<T, E = Error> = result::Result<T, E>;
/// General purpose actix web error
/// General purpose actix web error.
///
/// An actix web error is used to carry errors from `failure` or `std::error`
/// through actix in a convenient way. It can be created through through
/// converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created
/// for it that can be used to create an http response from it this means that
/// if you have access to an actix `Error` you can always get a
/// `ResponseError` reference from it.
pub struct Error {
cause: Box<ResponseError>,
backtrace: Option<Backtrace>,
}
impl Error {
/// Returns a reference to the underlying cause of this Error.
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
/// Deprecated way to reference the underlying response error.
#[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")]
pub fn cause(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the underlying cause of this `Error` as `Fail`
pub fn as_fail(&self) -> &Fail {
self.cause.as_fail()
}
/// Returns the reference to the underlying `ResponseError`.
pub fn as_response_error(&self) -> &ResponseError {
self.cause.as_ref()
}
/// Returns a reference to the Backtrace carried by this error, if it
/// carries one.
///
/// This uses the same `Backtrace` type that `failure` uses.
pub fn backtrace(&self) -> &Backtrace {
if let Some(bt) = self.cause.backtrace() {
bt
@ -56,10 +77,61 @@ impl Error {
self.backtrace.as_ref().unwrap()
}
}
/// Attempts to downcast this `Error` to a particular `Fail` type by reference.
///
/// If the underlying error is not of type `T`, this will return `None`.
pub fn downcast_ref<T: Fail>(&self) -> Option<&T> {
// in the most trivial way the cause is directly of the requested type.
if let Some(rv) = Fail::downcast_ref(self.cause.as_fail()) {
return Some(rv);
}
// in the more complex case the error has been constructed from a failure
// error. This happens because we implement From<failure::Error> by
// calling compat() and then storing it here. In failure this is
// represented by a failure::Error being wrapped in a failure::Compat.
//
// So we first downcast into that compat, to then further downcast through
// the failure's Error downcasting system into the original failure.
//
// This currently requires a transmute. This could be avoided if failure
// provides a deref: https://github.com/rust-lang-nursery/failure/pull/213
let compat: Option<&failure::Compat<failure::Error>> = Fail::downcast_ref(self.cause.as_fail());
if let Some(compat) = compat {
pub struct CompatWrappedError {
error: failure::Error,
}
let compat: &CompatWrappedError = unsafe {
::std::mem::transmute(compat)
};
compat.error.downcast_ref()
} else {
None
}
}
}
/// Helper trait to downcast a response error into a fail.
///
/// This is currently not exposed because it's unclear if this is the best way to
/// achieve the downcasting on `Error` for which this is needed.
#[doc(hidden)]
pub trait InternalResponseErrorAsFail {
#[doc(hidden)]
fn as_fail(&self) -> &Fail;
#[doc(hidden)]
fn as_mut_fail(&mut self) -> &mut Fail;
}
#[doc(hidden)]
impl<T: ResponseError> InternalResponseErrorAsFail for T {
fn as_fail(&self) -> &Fail { self }
fn as_mut_fail(&mut self) -> &mut Fail { self }
}
/// Error that can be converted to `HttpResponse`
pub trait ResponseError: Fail {
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
/// Create response for error
///
/// Internal server error is generated by default.
@ -853,7 +925,7 @@ mod tests {
}
#[test]
fn test_cause() {
fn test_as_fail() {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = ParseError::Io(orig);
@ -871,7 +943,7 @@ mod tests {
let orig = io::Error::new(io::ErrorKind::Other, "other");
let desc = orig.description().to_owned();
let e = Error::from(orig);
assert_eq!(format!("{}", e.cause()), desc);
assert_eq!(format!("{}", e.as_fail()), desc);
}
#[test]
@ -970,6 +1042,32 @@ mod tests {
assert_eq!(resp.status(), StatusCode::OK);
}
#[test]
fn test_error_downcasting_direct() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = DemoError.into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_downcasting_compat() {
#[derive(Debug, Fail)]
#[fail(display = "demo error")]
struct DemoError;
impl ResponseError for DemoError {}
let err: Error = failure::Error::from(DemoError).into();
let err_ref: &DemoError = err.downcast_ref().unwrap();
assert_eq!(err_ref.to_string(), "demo error");
}
#[test]
fn test_error_helpers() {
let r: HttpResponse = ErrorBadRequest("err").into();

View File

@ -89,7 +89,7 @@ impl HttpResponse {
/// Constructs an error response
#[inline]
pub fn from_error(error: Error) -> HttpResponse {
let mut resp = error.cause().error_response();
let mut resp = error.as_response_error().error_response();
resp.get_mut().error = Some(error);
resp
}