1
0
mirror of https://github.com/actix/actix-extras.git synced 2024-11-30 18:34:36 +01:00

make ErrorBadRequest type useful

This commit is contained in:
Nikolay Kim 2017-12-08 15:25:37 -08:00
parent 9043e7286d
commit a44f71d8c2
9 changed files with 208 additions and 48 deletions

View File

@ -16,6 +16,7 @@ fn main() {
"guide/src/qs_2.md", "guide/src/qs_2.md",
"guide/src/qs_3.md", "guide/src/qs_3.md",
"guide/src/qs_4.md", "guide/src/qs_4.md",
"guide/src/qs_4_5.md",
"guide/src/qs_5.md", "guide/src/qs_5.md",
"guide/src/qs_6.md", "guide/src/qs_6.md",
"guide/src/qs_7.md", "guide/src/qs_7.md",

View File

@ -4,6 +4,7 @@
- [Getting Started](./qs_2.md) - [Getting Started](./qs_2.md)
- [Application](./qs_3.md) - [Application](./qs_3.md)
- [Handler](./qs_4.md) - [Handler](./qs_4.md)
- [Errors](./qs_4_5.md)
- [State](./qs_6.md) - [State](./qs_6.md)
- [Resources and Routes](./qs_5.md) - [Resources and Routes](./qs_5.md)
- [Request & Response](./qs_7.md) - [Request & Response](./qs_7.md)

View File

@ -84,9 +84,7 @@ fn main() {
.serve::<_, ()>("127.0.0.1:8088").unwrap(); .serve::<_, ()>("127.0.0.1:8088").unwrap();
println!("Started http server: 127.0.0.1:8088"); 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(); let _ = sys.run();
} }
``` ```

View File

@ -1,21 +1,19 @@
# Handler # Handler
A request handler can by any object that implements 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: By default actix provides several `FromRequest` implementations for some standard types,
* Simple function that accepts `HttpRequest` and returns any object that
implements `FromRequest` trait
* Function that accepts `HttpRequest` and returns `Result<Reply, Into<Error>>` object.
* Function that accepts `HttpRequest` and return actor that has `HttpContext<A>`as a context.
Actix provides response `FromRequest` implementation for some standard types,
like `&'static str`, `String`, etc. like `&'static str`, `String`, etc.
For complete list of implementations check For complete list of implementations check
[FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls). [FromRequest documentation](../actix_web/trait.FromRequest.html#foreign-impls).
Examples: Examples of valid handlers:
```rust,ignore ```rust,ignore
fn index(req: HttpRequest) -> &'static str { fn index(req: HttpRequest) -> &'static str {
@ -41,9 +39,11 @@ fn index(req: HttpRequest) -> Box<Future<Item=HttpResponse, Error=Error>> {
} }
``` ```
## 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 ```rust
# extern crate actix; # extern crate actix;
@ -55,7 +55,7 @@ use actix_web::*;
#[derive(Serialize)] #[derive(Serialize)]
struct MyObj { struct MyObj {
name: String, name: &'static str,
} }
/// we have to convert Error into HttpResponse as well /// 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() { fn main() {
let sys = actix::System::new("example"); let sys = actix::System::new("example");
HttpServer::new( HttpServer::new(
Application::new("/") Application::new("/")
.resource("/", |r| r.method( .resource("/", |r| r.method(Method::GET).f(index)))
Method::GET).f(|req| {MyObj{name: "user".to_owned()}})))
.serve::<_, ()>("127.0.0.1:8088").unwrap(); .serve::<_, ()>("127.0.0.1:8088").unwrap();
println!("Started http server: 127.0.0.1:8088"); 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(); let _ = sys.run();
} }
``` ```

135
guide/src/qs_4_5.md Normal file
View File

@ -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<T: FromRequest, E: Into<Error>> FromRequest for Result<T, E>
```
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<fs::NamedFile> {
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.

View File

@ -426,6 +426,50 @@ impl From<UrlParseError> 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<fs::NamedFile> {
/// let f = NamedFile::open("test.txt").map_err(error::ErrorBadRequest)?;
/// Ok(f)
/// }
/// # fn main() {}
/// ```
#[derive(Debug)]
pub struct ErrorBadRequest<T>(pub T);
unsafe impl<T> Sync for ErrorBadRequest<T> {}
unsafe impl<T> Send for ErrorBadRequest<T> {}
impl<T> ErrorBadRequest<T> {
pub fn cause(&self) -> &T {
&self.0
}
}
impl<T: fmt::Debug + 'static> Fail for ErrorBadRequest<T> {}
impl<T: fmt::Debug + 'static> fmt::Display for ErrorBadRequest<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "BadRequest({:?})", self.0)
}
}
impl<T> ResponseError for ErrorBadRequest<T>
where T: Send + Sync + fmt::Debug + 'static,
{
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use std::error::Error as StdError; use std::error::Error as StdError;

View File

@ -81,7 +81,7 @@ pub mod httpcodes;
pub mod multipart; pub mod multipart;
pub mod middlewares; pub mod middlewares;
pub mod pred; pub mod pred;
pub use error::{Error, Result}; pub use error::{Error, Result, ResponseError};
pub use body::{Body, Binary}; pub use body::{Body, Binary};
pub use application::Application; pub use application::Application;
pub use httprequest::HttpRequest; pub use httprequest::HttpRequest;

View File

@ -2,14 +2,9 @@ use std;
use std::ops::Index; use std::ops::Index;
use std::path::PathBuf; use std::path::PathBuf;
use std::str::FromStr; use std::str::FromStr;
use failure::Fail;
use http::{StatusCode};
use smallvec::SmallVec; use smallvec::SmallVec;
use body::Body; use error::{ResponseError, UriSegmentError, ErrorBadRequest};
use httpresponse::HttpResponse;
use error::{ResponseError, UriSegmentError};
/// A trait to abstract the idea of creating a new instance of a type from a path parameter. /// 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>(T);
impl<T> BadRequest<T> {
pub fn cause(&self) -> &T {
&self.0
}
}
impl<T> ResponseError for BadRequest<T>
where T: Send + Sync + std::fmt::Debug +std::fmt::Display + 'static,
BadRequest<T>: Fail
{
fn error_response(&self) -> HttpResponse {
HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
}
}
macro_rules! FROM_STR { macro_rules! FROM_STR {
($type:ty) => { ($type:ty) => {
impl FromParam for $type { impl FromParam for $type {
type Err = BadRequest<<$type as FromStr>::Err>; type Err = ErrorBadRequest<<$type as FromStr>::Err>;
fn from_param(val: &str) -> Result<Self, Self::Err> { fn from_param(val: &str) -> Result<Self, Self::Err> {
<$type as FromStr>::from_str(val).map_err(BadRequest) <$type as FromStr>::from_str(val).map_err(ErrorBadRequest)
} }
} }
} }

View File

@ -169,6 +169,9 @@ mod tests {
let pred = Header("transfer-encoding", "other"); let pred = Header("transfer-encoding", "other");
assert!(!pred.check(&mut req)); assert!(!pred.check(&mut req));
let pred = Header("content-tye", "other");
assert!(!pred.check(&mut req));
} }
#[test] #[test]