From a44f71d8c27b88e57a9d4573b657c16009ca8a9a Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 8 Dec 2017 15:25:37 -0800 Subject: [PATCH] make ErrorBadRequest type useful --- build.rs | 1 + guide/src/SUMMARY.md | 1 + guide/src/qs_2.md | 4 +- guide/src/qs_4.md | 36 ++++++------ guide/src/qs_4_5.md | 135 +++++++++++++++++++++++++++++++++++++++++++ src/error.rs | 44 ++++++++++++++ src/lib.rs | 2 +- src/param.rs | 30 +--------- src/pred.rs | 3 + 9 files changed, 208 insertions(+), 48 deletions(-) create mode 100644 guide/src/qs_4_5.md diff --git a/build.rs b/build.rs index 6a8a3bd0..3b916a95 100644 --- a/build.rs +++ b/build.rs @@ -16,6 +16,7 @@ fn main() { "guide/src/qs_2.md", "guide/src/qs_3.md", "guide/src/qs_4.md", + "guide/src/qs_4_5.md", "guide/src/qs_5.md", "guide/src/qs_6.md", "guide/src/qs_7.md", diff --git a/guide/src/SUMMARY.md b/guide/src/SUMMARY.md index 85332cf4..17ce202e 100644 --- a/guide/src/SUMMARY.md +++ b/guide/src/SUMMARY.md @@ -4,6 +4,7 @@ - [Getting Started](./qs_2.md) - [Application](./qs_3.md) - [Handler](./qs_4.md) +- [Errors](./qs_4_5.md) - [State](./qs_6.md) - [Resources and Routes](./qs_5.md) - [Request & Response](./qs_7.md) diff --git a/guide/src/qs_2.md b/guide/src/qs_2.md index f8187c70..cd32a87b 100644 --- a/guide/src/qs_2.md +++ b/guide/src/qs_2.md @@ -84,9 +84,7 @@ fn main() { .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); - // do not copy this line - actix::Arbiter::system().send(actix::msgs::SystemExit(0)); - +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); let _ = sys.run(); } ``` diff --git a/guide/src/qs_4.md b/guide/src/qs_4.md index ae5ae19c..acab60d7 100644 --- a/guide/src/qs_4.md +++ b/guide/src/qs_4.md @@ -1,21 +1,19 @@ # Handler A request handler can by any object that implements -[`Handler` trait](../actix_web/struct.HttpResponse.html#implementations). +[`Handler` trait](../actix_web/dev/trait.Handler.html#implementors). +Request handling happen in two stages. First handler object get called. +Handle can return any object that implements +[`FromRequest` trait](../actix_web/trait.FromRequest.html#foreign-impls). +Then `from_request()` get called on returned object. And finally +result of the `from_request()` call get converted to `Reply` object. -By default actix provdes several `Handler` implementations: - -* Simple function that accepts `HttpRequest` and returns any object that - implements `FromRequest` trait -* Function that accepts `HttpRequest` and returns `Result>` object. -* Function that accepts `HttpRequest` and return actor that has `HttpContext`as a context. - -Actix provides response `FromRequest` implementation for some standard types, +By default actix provides several `FromRequest` implementations for some standard types, like `&'static str`, `String`, etc. For complete list of implementations check [FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls). -Examples: +Examples of valid handlers: ```rust,ignore fn index(req: HttpRequest) -> &'static str { @@ -41,9 +39,11 @@ fn index(req: HttpRequest) -> Box> { } ``` -## Custom conversion +## Response with custom type -Let's create response for custom type that serializes to `application/json` response: +To return custom type directly from handler function `FromResponse` trait should be +implemented for this type. Let's create response for custom type that +serializes to `application/json` response: ```rust # extern crate actix; @@ -55,7 +55,7 @@ use actix_web::*; #[derive(Serialize)] struct MyObj { - name: String, + name: &'static str, } /// we have to convert Error into HttpResponse as well @@ -73,18 +73,20 @@ impl FromRequest for MyObj { } } +fn index(req: HttpRequest) -> MyObj { + MyObj{name: "user"} +} + fn main() { let sys = actix::System::new("example"); HttpServer::new( Application::new("/") - .resource("/", |r| r.method( - Method::GET).f(|req| {MyObj{name: "user".to_owned()}}))) + .resource("/", |r| r.method(Method::GET).f(index))) .serve::<_, ()>("127.0.0.1:8088").unwrap(); println!("Started http server: 127.0.0.1:8088"); - actix::Arbiter::system().send(actix::msgs::SystemExit(0)); // <- remove this line, this code stops system during testing - +# actix::Arbiter::system().send(actix::msgs::SystemExit(0)); let _ = sys.run(); } ``` diff --git a/guide/src/qs_4_5.md b/guide/src/qs_4_5.md new file mode 100644 index 00000000..ad815436 --- /dev/null +++ b/guide/src/qs_4_5.md @@ -0,0 +1,135 @@ +# Errors + +Actix uses [`Error` type](../actix_web/error/struct.Error.html) +and [`ResponseError` trait](../actix_web/error/trait.ResponseError.html) +for handling handler's errors. +Any error that implements `ResponseError` trait can be returned as error value. +*Handler* can return *Result* object, actix by default provides +`FromRequest` implemenation for compatible result object. Here is implementation +definition: + +```rust,ignore +impl> FromRequest for Result +``` + +And any error that implements `ResponseError` can be converted into `Error` object. +For example if *handler* function returns `io::Error`, it would be converted +into `HTTPInternalServerError` response. Implementation for `io::Error` is provided +by default. + +```rust +# extern crate actix_web; +# use actix_web::*; +use std::io; + +fn index(req: HttpRequest) -> io::Result { + Ok(fs::NamedFile::open("static/index.html")?) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +## Custom error response + +To add support for custom errors all we need to do just implement `ResponseError` trait. +`ResponseError` trait has default implementation for `error_response()` method, it +generates *500* response. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Fail, Debug)] +#[fail(display="my error")] +struct MyError { + name: &'static str +} + +impl error::ResponseError for MyError {} + +fn index(req: HttpRequest) -> Result<&'static str, MyError> { + Err(MyError{name: "test"}) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +In this example *index* handler will always return *500* response. But it is easy +to return different responses. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Fail, Debug)] +enum MyError { + #[fail(display="internal error")] + InternalError, + #[fail(display="bad request")] + BadClientData, + #[fail(display="timeout")] + Timeout, +} + +impl error::ResponseError for MyError { + fn error_response(&self) -> HttpResponse { + match *self { + MyError::InternalError => HttpResponse::new( + StatusCode::INTERNAL_SERVER_ERROR, Body::Empty), + MyError::BadClientData => HttpResponse::new( + StatusCode::BAD_REQUEST, Body::Empty), + MyError::Timeout => HttpResponse::new( + StatusCode::GATEWAY_TIMEOUT, Body::Empty), + } + } +} + +fn index(req: HttpRequest) -> Result<&'static str, MyError> { + Err(MyError::BadClientData) +} +# +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +## Error helpers + +Actix provides set of error helper types. It is possible to use them to generate +specific error response. We can use helper types for first example with custom error. + +```rust +# extern crate actix_web; +#[macro_use] extern crate failure; +use actix_web::*; + +#[derive(Debug)] +struct MyError { + name: &'static str +} + +fn index(req: HttpRequest) -> Result<&'static str> { + let result: Result<&'static str, MyError> = Err(MyError{name: "test"}); + + Ok(result.map_err(error::ErrorBadRequest)?) +} +# fn main() { +# Application::new("/") +# .resource(r"/a/index.html", |r| r.f(index)) +# .finish(); +# } +``` + +In this example *BAD REQUEST* response get generated for `MYError` error. diff --git a/src/error.rs b/src/error.rs index d2434b6d..a45b6b6f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -426,6 +426,50 @@ impl From for UrlGenerationError { } } + +/// Helper type that can wrap any error and generate *BAD REQUEST* response. +/// +/// In following example any `io::Error` will be converted into "BAD REQUEST" response +/// as oposite to *INNTERNAL SERVER ERROR* which is defined by default. +/// +/// ```rust +/// # extern crate actix_web; +/// # use actix_web::*; +/// use actix_web::fs::NamedFile; +/// +/// fn index(req: HttpRequest) -> Result { +/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?; +/// Ok(f) +/// } +/// # fn main() {} +/// ``` +#[derive(Debug)] +pub struct ErrorBadRequest(pub T); + +unsafe impl Sync for ErrorBadRequest {} +unsafe impl Send for ErrorBadRequest {} + +impl ErrorBadRequest { + pub fn cause(&self) -> &T { + &self.0 + } +} + +impl Fail for ErrorBadRequest {} +impl fmt::Display for ErrorBadRequest { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "BadRequest({:?})", self.0) + } +} + +impl ResponseError for ErrorBadRequest + where T: Send + Sync + fmt::Debug + 'static, +{ + fn error_response(&self) -> HttpResponse { + HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) + } +} + #[cfg(test)] mod tests { use std::error::Error as StdError; diff --git a/src/lib.rs b/src/lib.rs index 78aa9c57..d1b9feb8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,7 +81,7 @@ pub mod httpcodes; pub mod multipart; pub mod middlewares; pub mod pred; -pub use error::{Error, Result}; +pub use error::{Error, Result, ResponseError}; pub use body::{Body, Binary}; pub use application::Application; pub use httprequest::HttpRequest; diff --git a/src/param.rs b/src/param.rs index 3981a81b..63c37f13 100644 --- a/src/param.rs +++ b/src/param.rs @@ -2,14 +2,9 @@ use std; use std::ops::Index; use std::path::PathBuf; use std::str::FromStr; - -use failure::Fail; -use http::{StatusCode}; use smallvec::SmallVec; -use body::Body; -use httpresponse::HttpResponse; -use error::{ResponseError, UriSegmentError}; +use error::{ResponseError, UriSegmentError, ErrorBadRequest}; /// A trait to abstract the idea of creating a new instance of a type from a path parameter. @@ -132,32 +127,13 @@ impl FromParam for PathBuf { } } -#[derive(Fail, Debug)] -#[fail(display="Error")] -pub struct BadRequest(T); - -impl BadRequest { - pub fn cause(&self) -> &T { - &self.0 - } -} - -impl ResponseError for BadRequest - where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static, -BadRequest: Fail -{ - fn error_response(&self) -> HttpResponse { - HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty) - } -} - macro_rules! FROM_STR { ($type:ty) => { impl FromParam for $type { - type Err = BadRequest<<$type as FromStr>::Err>; + type Err = ErrorBadRequest<<$type as FromStr>::Err>; fn from_param(val: &str) -> Result { - <$type as FromStr>::from_str(val).map_err(BadRequest) + <$type as FromStr>::from_str(val).map_err(ErrorBadRequest) } } } diff --git a/src/pred.rs b/src/pred.rs index b760af28..b3fc6f88 100644 --- a/src/pred.rs +++ b/src/pred.rs @@ -169,6 +169,9 @@ mod tests { let pred = Header("transfer-encoding", "other"); assert!(!pred.check(&mut req)); + + let pred = Header("content-tye", "other"); + assert!(!pred.check(&mut req)); } #[test]