mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-27 17:52:56 +01:00
Query config (#839)
* add QueryConfig * expose QueryConfig in web module * fmt * use associated type for QueryConfig * update CHANGES.md
This commit is contained in:
parent
f8af3b86e5
commit
bba90d7f22
@ -1,5 +1,9 @@
|
|||||||
# Changes
|
# Changes
|
||||||
|
|
||||||
|
### Add
|
||||||
|
|
||||||
|
* `QueryConfig`, similar to `JsonConfig` for customizing error handling of query extractors.
|
||||||
|
|
||||||
### Changes
|
### Changes
|
||||||
|
|
||||||
* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too.
|
* `JsonConfig` is now `Send + Sync`, this implies that `error_handler` must be `Send + Sync` too.
|
||||||
|
24
src/error.rs
24
src/error.rs
@ -6,6 +6,7 @@ use url::ParseError as UrlParseError;
|
|||||||
|
|
||||||
use crate::http::StatusCode;
|
use crate::http::StatusCode;
|
||||||
use crate::HttpResponse;
|
use crate::HttpResponse;
|
||||||
|
use serde_urlencoded::de;
|
||||||
|
|
||||||
/// Errors which can occur when attempting to generate resource uri.
|
/// Errors which can occur when attempting to generate resource uri.
|
||||||
#[derive(Debug, PartialEq, Display, From)]
|
#[derive(Debug, PartialEq, Display, From)]
|
||||||
@ -91,6 +92,23 @@ impl ResponseError for JsonPayloadError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of errors that can occur during parsing query strings
|
||||||
|
#[derive(Debug, Display, From)]
|
||||||
|
pub enum QueryPayloadError {
|
||||||
|
/// Deserialize error
|
||||||
|
#[display(fmt = "Query deserialize error: {}", _0)]
|
||||||
|
Deserialize(de::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `BadRequest` for `QueryPayloadError`
|
||||||
|
impl ResponseError for QueryPayloadError {
|
||||||
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
match *self {
|
||||||
|
QueryPayloadError::Deserialize(_) => HttpResponse::new(StatusCode::BAD_REQUEST),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Error type returned when reading body as lines.
|
/// Error type returned when reading body as lines.
|
||||||
#[derive(From, Display, Debug)]
|
#[derive(From, Display, Debug)]
|
||||||
pub enum ReadlinesError {
|
pub enum ReadlinesError {
|
||||||
@ -143,6 +161,12 @@ mod tests {
|
|||||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_query_payload_error() {
|
||||||
|
let resp: HttpResponse = QueryPayloadError::Deserialize(serde_urlencoded::from_str::<i32>("bad query").unwrap_err()).error_response();
|
||||||
|
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_readlines_error() {
|
fn test_readlines_error() {
|
||||||
let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response();
|
let resp: HttpResponse = ReadlinesError::LimitOverflow.error_response();
|
||||||
|
@ -11,4 +11,4 @@ pub use self::form::{Form, FormConfig};
|
|||||||
pub use self::json::{Json, JsonConfig};
|
pub use self::json::{Json, JsonConfig};
|
||||||
pub use self::path::Path;
|
pub use self::path::Path;
|
||||||
pub use self::payload::{Payload, PayloadConfig};
|
pub use self::payload::{Payload, PayloadConfig};
|
||||||
pub use self::query::Query;
|
pub use self::query::{Query, QueryConfig};
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
//! Query extractor
|
//! Query extractor
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
use std::{fmt, ops};
|
use std::{fmt, ops};
|
||||||
|
|
||||||
use actix_http::error::Error;
|
use actix_http::error::Error;
|
||||||
@ -9,6 +10,7 @@ use serde_urlencoded;
|
|||||||
use crate::dev::Payload;
|
use crate::dev::Payload;
|
||||||
use crate::extract::FromRequest;
|
use crate::extract::FromRequest;
|
||||||
use crate::request::HttpRequest;
|
use crate::request::HttpRequest;
|
||||||
|
use crate::error::QueryPayloadError;
|
||||||
|
|
||||||
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
#[derive(PartialEq, Eq, PartialOrd, Ord)]
|
||||||
/// Extract typed information from from the request's query.
|
/// Extract typed information from from the request's query.
|
||||||
@ -115,32 +117,103 @@ impl<T> FromRequest for Query<T>
|
|||||||
where
|
where
|
||||||
T: de::DeserializeOwned,
|
T: de::DeserializeOwned,
|
||||||
{
|
{
|
||||||
type Config = ();
|
|
||||||
type Error = Error;
|
type Error = Error;
|
||||||
type Future = Result<Self, Error>;
|
type Future = Result<Self, Error>;
|
||||||
|
type Config = QueryConfig;
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||||
|
let error_handler = req
|
||||||
|
.app_data::<Self::Config>()
|
||||||
|
.map(|c| c.ehandler.clone())
|
||||||
|
.unwrap_or(None);
|
||||||
|
|
||||||
serde_urlencoded::from_str::<T>(req.query_string())
|
serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
.map(|val| Ok(Query(val)))
|
.map(|val| Ok(Query(val)))
|
||||||
.unwrap_or_else(|e| {
|
.unwrap_or_else(move |e| {
|
||||||
|
let e = QueryPayloadError::Deserialize(e);
|
||||||
|
|
||||||
log::debug!(
|
log::debug!(
|
||||||
"Failed during Query extractor deserialization. \
|
"Failed during Query extractor deserialization. \
|
||||||
Request path: {:?}",
|
Request path: {:?}",
|
||||||
req.path()
|
req.path()
|
||||||
);
|
);
|
||||||
Err(e.into())
|
|
||||||
|
let e = if let Some(error_handler) = error_handler {
|
||||||
|
(error_handler)(e, req)
|
||||||
|
} else {
|
||||||
|
e.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
Err(e)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Query extractor configuration
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// /// deserialize `Info` from request's querystring
|
||||||
|
/// fn index(info: web::Query<Info>) -> String {
|
||||||
|
/// format!("Welcome {}!", info.username)
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = App::new().service(
|
||||||
|
/// web::resource("/index.html").data(
|
||||||
|
/// // change query extractor configuration
|
||||||
|
/// web::Query::<Info>::configure(|cfg| {
|
||||||
|
/// cfg.error_handler(|err, req| { // <- create custom error response
|
||||||
|
/// error::InternalError::from_response(
|
||||||
|
/// err, HttpResponse::Conflict().finish()).into()
|
||||||
|
/// })
|
||||||
|
/// }))
|
||||||
|
/// .route(web::post().to(index))
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct QueryConfig {
|
||||||
|
ehandler: Option<Arc<Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryConfig {
|
||||||
|
/// Set custom error handler
|
||||||
|
pub fn error_handler<F>(mut self, f: F) -> Self
|
||||||
|
where
|
||||||
|
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
self.ehandler = Some(Arc::new(f));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for QueryConfig {
|
||||||
|
fn default() -> Self {
|
||||||
|
QueryConfig {
|
||||||
|
ehandler: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use derive_more::Display;
|
use derive_more::Display;
|
||||||
use serde_derive::Deserialize;
|
use serde_derive::Deserialize;
|
||||||
|
use actix_http::http::StatusCode;
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::TestRequest;
|
use crate::test::TestRequest;
|
||||||
|
use crate::error::InternalError;
|
||||||
|
use crate::HttpResponse;
|
||||||
|
|
||||||
#[derive(Deserialize, Debug, Display)]
|
#[derive(Deserialize, Debug, Display)]
|
||||||
struct Id {
|
struct Id {
|
||||||
@ -164,4 +237,19 @@ mod tests {
|
|||||||
let s = s.into_inner();
|
let s = s.into_inner();
|
||||||
assert_eq!(s.id, "test1");
|
assert_eq!(s.id, "test1");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_error_responder() {
|
||||||
|
let req = TestRequest::with_uri("/name/user1/")
|
||||||
|
.data(QueryConfig::default().error_handler(|e, _| {
|
||||||
|
let resp = HttpResponse::UnprocessableEntity().finish();
|
||||||
|
InternalError::from_response(e, resp).into()
|
||||||
|
})).to_srv_request();
|
||||||
|
|
||||||
|
let (req, mut pl) = req.into_parts();
|
||||||
|
let query = Query::<Id>::from_request(&req, &mut pl);
|
||||||
|
|
||||||
|
assert!(query.is_err());
|
||||||
|
assert_eq!(query.unwrap_err().as_response_error().error_response().status(), StatusCode::UNPROCESSABLE_ENTITY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user