mirror of
https://github.com/actix/actix-web.git
synced 2024-12-23 16:40:37 +00:00
Added improved failure interoperability with downcasting (#285)
Deprecates Error::cause and introduces failure interoperability functions and downcasting.
This commit is contained in:
parent
e140bc3906
commit
7119340d44
2 changed files with 105 additions and 7 deletions
110
src/error.rs
110
src/error.rs
|
@ -33,21 +33,42 @@ use httpresponse::HttpResponse;
|
||||||
/// `Result`.
|
/// `Result`.
|
||||||
pub type Result<T, E = Error> = result::Result<T, E>;
|
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 {
|
pub struct Error {
|
||||||
cause: Box<ResponseError>,
|
cause: Box<ResponseError>,
|
||||||
backtrace: Option<Backtrace>,
|
backtrace: Option<Backtrace>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Error {
|
impl Error {
|
||||||
/// Returns a reference to the underlying cause of this Error.
|
/// Deprecated way to reference the underlying response error.
|
||||||
// this should return &Fail but needs this https://github.com/rust-lang/rust/issues/5665
|
#[deprecated(since = "0.6.0", note = "please use `Error::as_response_error()` instead")]
|
||||||
pub fn cause(&self) -> &ResponseError {
|
pub fn cause(&self) -> &ResponseError {
|
||||||
self.cause.as_ref()
|
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
|
/// Returns a reference to the Backtrace carried by this error, if it
|
||||||
/// carries one.
|
/// carries one.
|
||||||
|
///
|
||||||
|
/// This uses the same `Backtrace` type that `failure` uses.
|
||||||
pub fn backtrace(&self) -> &Backtrace {
|
pub fn backtrace(&self) -> &Backtrace {
|
||||||
if let Some(bt) = self.cause.backtrace() {
|
if let Some(bt) = self.cause.backtrace() {
|
||||||
bt
|
bt
|
||||||
|
@ -55,10 +76,61 @@ impl Error {
|
||||||
self.backtrace.as_ref().unwrap()
|
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`
|
/// Error that can be converted to `HttpResponse`
|
||||||
pub trait ResponseError: Fail {
|
pub trait ResponseError: Fail + InternalResponseErrorAsFail {
|
||||||
/// Create response for error
|
/// Create response for error
|
||||||
///
|
///
|
||||||
/// Internal server error is generated by default.
|
/// Internal server error is generated by default.
|
||||||
|
@ -833,7 +905,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_cause() {
|
fn test_as_fail() {
|
||||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||||
let desc = orig.description().to_owned();
|
let desc = orig.description().to_owned();
|
||||||
let e = ParseError::Io(orig);
|
let e = ParseError::Io(orig);
|
||||||
|
@ -851,7 +923,7 @@ mod tests {
|
||||||
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
let orig = io::Error::new(io::ErrorKind::Other, "other");
|
||||||
let desc = orig.description().to_owned();
|
let desc = orig.description().to_owned();
|
||||||
let e = Error::from(orig);
|
let e = Error::from(orig);
|
||||||
assert_eq!(format!("{}", e.cause()), desc);
|
assert_eq!(format!("{}", e.as_fail()), desc);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -950,6 +1022,32 @@ mod tests {
|
||||||
assert_eq!(resp.status(), StatusCode::OK);
|
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]
|
#[test]
|
||||||
fn test_error_helpers() {
|
fn test_error_helpers() {
|
||||||
let r: HttpResponse = ErrorBadRequest("err").into();
|
let r: HttpResponse = ErrorBadRequest("err").into();
|
||||||
|
|
|
@ -89,7 +89,7 @@ impl HttpResponse {
|
||||||
/// Constructs a error response
|
/// Constructs a error response
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn from_error(error: Error) -> HttpResponse {
|
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.get_mut().error = Some(error);
|
||||||
resp
|
resp
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue