diff options
| author | daddinuz <daddinuz@gmail.com> | 2019-05-15 14:04:06 +0200 | 
|---|---|---|
| committer | daddinuz <daddinuz@gmail.com> | 2019-05-15 14:04:06 +0200 | 
| commit | 8f57d8ea722c390c1f02082b8c3c01c2c87d400f (patch) | |
| tree | f3f186fc7ea78d2ca93ee3910f254b4a25c35a90 | |
| parent | 10b3add38d8aa04dff789469449340ee7f817f32 (diff) | |
update actix support to v1
| -rw-r--r-- | Cargo.toml | 2 | ||||
| -rw-r--r-- | src/actix.rs | 312 | ||||
| -rw-r--r-- | tests/test_actix.rs | 147 | 
3 files changed, 258 insertions, 203 deletions
| @@ -16,7 +16,7 @@ version = "0.4.6"  repository = "samscott89/serde_qs"  [dependencies] -actix-web = { version ="0.7", optional = true } +actix-web = { version ="1.0.0-beta", optional = true }  data-encoding = "2.1.2"  error-chain = "0.12.0"  percent-encoding = "1.0.1" diff --git a/src/actix.rs b/src/actix.rs index 06cafc6..fdab33f 100644 --- a/src/actix.rs +++ b/src/actix.rs @@ -1,148 +1,164 @@ -//! Functionality for using `serde_qs` with `actix_web`.
 -//!
 -//! Enable with the `actix` feature.
 -
 -use actix_web::FromRequest;
 -use actix_web::HttpRequest;
 -use serde::de;
 -
 -use std::ops::{Deref, DerefMut};
 -use std::rc::Rc;
 -use std::fmt;
 -
 -use error::Error as QsError;
 -
 -#[derive(PartialEq, Eq, PartialOrd, Ord)]
 -/// Extract typed information from from the request's query.
 -/// `serde_qs` equivalent to `actix_web::Query`.
 -///
 -/// ## Example
 -///
 -/// ```rust
 -/// # extern crate actix_web;
 -/// # extern crate serde_qs;
 -/// #[macro_use] extern crate serde_derive;
 -/// use actix_web::{App, http};
 -/// use serde_qs::actix::QsQuery;
 -///
 -///#[derive(Deserialize)]
 -///pub struct Request {
 -///    id: Vec<u64>,
 -///}
 -///
 -/// // use `with` extractor for query info
 -/// // this handler get called only if request's query contains `username` field
 -/// // The correct request for this handler would be `/index.html?id[]=1&id[]=2"`
 -/// fn index(info: QsQuery<Request>) -> String {
 -///     format!("Request for client with list of ids={:?}", info.id)
 -/// }
 -///
 -/// fn main() {
 -///     let app = App::new().resource(
 -///        "/index.html",
 -///        |r| r.method(http::Method::GET).with(index)); // <- use `with` extractor
 -/// }
 -/// ```
 -pub struct QsQuery<T>(T);
 -
 -impl<T> Deref for QsQuery<T> {
 -    type Target = T;
 -
 -    fn deref(&self) -> &T {
 -        &self.0
 -    }
 -}
 -
 -impl<T> DerefMut for QsQuery<T> {
 -    fn deref_mut(&mut self) -> &mut T {
 -        &mut self.0
 -    }
 -}
 -
 -impl<T> QsQuery<T> {
 -    /// Deconstruct to a inner value
 -    pub fn into_inner(self) -> T {
 -        self.0
 -    }
 -}
 -
 -impl<T, S> FromRequest<S> for QsQuery<T>
 -where
 -    T: de::DeserializeOwned,
 -{
 -    type Config = QsQueryConfig<S>;
 -    type Result = Result<Self, actix_web::Error>;
 -
 -    #[inline]
 -    fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
 -        let req2 = req.clone();
 -        let err = Rc::clone(&cfg.ehandler);
 -        super::from_str::<T>(req.query_string())
 -            .map_err(move |e| (*err)(e, &req2))
 -            .map(QsQuery)
 -    }
 -}
 -
 -/// QsQuery extractor configuration
 -///
 -/// ```rust
 -/// # extern crate actix_web;
 -/// # extern crate serde_qs;
 -/// #[macro_use] extern crate serde_derive;
 -/// use actix_web::{error, http, App, HttpResponse, Result};
 -/// use serde_qs::actix::QsQuery;
 -///
 -/// #[derive(Deserialize)]
 -/// struct Info {
 -///     username: String,
 -/// }
 -///
 -/// /// deserialize `Info` from request's body, max payload size is 4kb
 -/// fn index(info: QsQuery<Info>) -> Result<String> {
 -///     Ok(format!("Welcome {}!", info.username))
 -/// }
 -///
 -/// fn main() {
 -///     let app = App::new().resource("/index.html", |r| {
 -///         r.method(http::Method::GET).with_config(index, |cfg| {
 -///             cfg.0.error_handler(|err, req| {
 -///                 // <- create custom error response
 -///                 error::InternalError::from_response(err.description().to_string(), HttpResponse::Conflict().finish()).into()
 -///             });
 -///         })
 -///     });
 -/// }
 -/// ```
 -pub struct QsQueryConfig<S> {
 -    ehandler: Rc<Fn(QsError, &HttpRequest<S>) -> actix_web::Error>,
 -}
 -impl<S> QsQueryConfig<S> {
 -    /// Set custom error handler
 -    pub fn error_handler<F>(&mut self, f: F) -> &mut Self
 -    where
 -        F: Fn(QsError, &HttpRequest<S>) -> actix_web::Error + 'static,
 -    {
 -        self.ehandler = Rc::new(f);
 -        self
 -    }
 -}
 -
 -impl<S> Default for QsQueryConfig<S> {
 -    fn default() -> Self {
 -        QsQueryConfig {
 -            ehandler: Rc::new(|_, _| actix_web::error::UrlencodedError::Parse.into()),
 -        }
 -    }
 -}
 -
 -impl<T: fmt::Debug> fmt::Debug for QsQuery<T> {
 -    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 -        self.0.fmt(f)
 -    }
 -}
 -
 -impl<T: fmt::Display> fmt::Display for QsQuery<T> {
 -    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
 -        self.0.fmt(f)
 -    }
 -}
 +//! Functionality for using `serde_qs` with `actix_web`. +//! +//! Enable with the `actix` feature. + +use actix_web::dev::Payload; +use actix_web::{Error as ActixError, FromRequest, HttpRequest, HttpResponse, ResponseError}; +use error::Error as QsError; +use serde::de; +use std::fmt; +use std::fmt::{Debug, Display}; +use std::ops::{Deref, DerefMut}; +use std::sync::Arc; + +impl ResponseError for QsError { +    fn error_response(&self) -> HttpResponse { +        HttpResponse::BadRequest().finish() +    } +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +/// Extract typed information from from the request's query. +/// +/// ## Example +/// +/// ```rust +/// #[macro_use] extern crate serde_derive; +/// extern crate actix_web; +/// use actix_web::{web, App}; +/// use serde_qs::actix::QsQuery; +/// +/// #[derive(Deserialize)] +/// pub struct UsersFilter { +///    id: Vec<u64>, +/// } +/// +/// // Use `QsQuery` extractor for query information. +/// // The correct request for this handler would be `/users?id[]=1124&id[]=88"` +/// fn filter_users(info: QsQuery<UsersFilter>) -> String { +///     info.id.iter().map(|i| i.to_string()).collect::<Vec<String>>().join(", ").into() +/// } +/// +/// fn main() { +///     let app = App::new().service( +///        web::resource("/users") +///            .route(web::get().to(filter_users))); +/// } +/// ``` +pub struct QsQuery<T>(T); + +impl<T> Deref for QsQuery<T> { +    type Target = T; + +    fn deref(&self) -> &T { +        &self.0 +    } +} + +impl<T> DerefMut for QsQuery<T> { +    fn deref_mut(&mut self) -> &mut T { +        &mut self.0 +    } +} + +impl<T> QsQuery<T> { +    /// Deconstruct to a inner value +    pub fn into_inner(self) -> T { +        self.0 +    } +} + +impl<T: Debug> Debug for QsQuery<T> { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        self.0.fmt(f) +    } +} + +impl<T: Display> Display for QsQuery<T> { +    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +        self.0.fmt(f) +    } +} + +impl<T> FromRequest for QsQuery<T> +where +    T: de::DeserializeOwned, +{ +    type Error = ActixError; +    type Future = Result<Self, ActixError>; +    type Config = QsQueryConfig; + +    #[inline] +    fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { +        let error_handler = req +            .app_data::<QsQueryConfig>() +            .map(|c| c.ehandler.clone()) +            .unwrap_or(None); + +        super::from_str::<T>(req.query_string()) +            .map(|val| Ok(QsQuery(val))) +            .unwrap_or_else(move |e| { +                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; +/// extern crate actix_web; +/// use actix_web::{error, web, App, FromRequest, HttpResponse}; +/// use serde_qs::actix::QsQuery; +/// +/// #[derive(Deserialize)] +/// struct Info { +///     username: String, +/// } +/// +/// /// deserialize `Info` from request's querystring +/// fn index(info: QsQuery<Info>) -> String { +///     format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +///     let app = App::new().service( +///         web::resource("/index.html").data( +///             // change query extractor configuration +///             QsQuery::<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)) +///     ); +/// } +/// ``` +pub struct QsQueryConfig { +    ehandler: +        Option<Arc<Fn(QsError, &HttpRequest) -> ActixError + Send + Sync>>, +} + +impl QsQueryConfig { +    /// Set custom error handler +    pub fn error_handler<F>(mut self, f: F) -> Self +    where +        F: Fn(QsError, &HttpRequest) -> ActixError + Send + Sync + 'static, +    { +        self.ehandler = Some(Arc::new(f)); +        self +    } +} + +impl Default for QsQueryConfig { +    fn default() -> Self { +        QsQueryConfig { ehandler: None } +    } +} diff --git a/tests/test_actix.rs b/tests/test_actix.rs index 1e2d952..09bc015 100644 --- a/tests/test_actix.rs +++ b/tests/test_actix.rs @@ -1,54 +1,93 @@ -#![cfg(feature = "actix")]
 -
 -extern crate actix_web;
 -extern crate serde;
 -#[macro_use]
 -extern crate serde_derive;
 -extern crate serde_qs as qs;
 -
 -use actix_web::test::TestServer;
 -use qs::actix::QsQuery;
 -use serde::de::Error;
 -
 -fn from_str<'de, D, S>(deserializer: D) -> Result<S, D::Error>
 -    where D: serde::Deserializer<'de>,
 -          S: std::str::FromStr
 -{
 -    let s = <&str as serde::Deserialize>::deserialize(deserializer)?;
 -    S::from_str(&s).map_err(|_| D::Error::custom("could not parse string"))
 -}
 -
 -#[derive(Deserialize, Serialize, Debug, PartialEq)]
 -struct Query {
 -	foo: u64,
 -	bars: Vec<u64>,
 -    #[serde(flatten)]
 -    common: CommonParams,
 -}
 -
 -#[derive(Deserialize, Serialize, Debug, PartialEq)]
 -struct CommonParams {
 -    #[serde(deserialize_with="from_str")]
 -    limit: u64,
 -    #[serde(deserialize_with="from_str")]
 -    offset: u64,
 -    #[serde(deserialize_with="from_str")]
 -    remaining: bool,
 -}
 -
 -fn my_handler(query: QsQuery<Query>) -> String {
 -	println!("Query: {:?}", query);
 -	format!("Received bars: {:?}", query.bars)
 -}
 -
 -#[test]
 -fn test_qsquery() {
 -	let mut srv = TestServer::new(|app| {
 -		app.resource("/test", |h| h.with(my_handler));
 -	});
 -	let query = "/test?foo=1&bars[]=0&bars[]=1&limit=100&offset=50&remaining=true";
 -	let url = srv.url(query);
 -	let req = actix_web::client::get(url).finish().unwrap();
 -	let response = srv.execute(req.send()).unwrap();
 -	assert!(response.status().is_success());
 -}
\ No newline at end of file +#![cfg(feature = "actix")] + +extern crate actix_web; +extern crate serde; + +#[macro_use] +extern crate serde_derive; +extern crate serde_qs as qs; + +use actix_web::error::InternalError; +use actix_web::http::StatusCode; +use actix_web::test::TestRequest; +use actix_web::{FromRequest, HttpResponse}; +use qs::actix::{QsQuery, QsQueryConfig}; +use serde::de::Error; + +fn from_str<'de, D, S>(deserializer: D) -> Result<S, D::Error> +where +    D: serde::Deserializer<'de>, +    S: std::str::FromStr, +{ +    let s = <&str as serde::Deserialize>::deserialize(deserializer)?; +    S::from_str(&s).map_err(|_| D::Error::custom("could not parse string")) +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +struct Query { +    foo: u64, +    bars: Vec<u64>, +    #[serde(flatten)] +    common: CommonParams, +} + +#[derive(Deserialize, Serialize, Debug, PartialEq)] +struct CommonParams { +    #[serde(deserialize_with = "from_str")] +    limit: u64, +    #[serde(deserialize_with = "from_str")] +    offset: u64, +    #[serde(deserialize_with = "from_str")] +    remaining: bool, +} + +#[test] +fn test_default_error_handler() { +    let req = TestRequest::with_uri("/test").to_srv_request(); +    let (req, mut pl) = req.into_parts(); + +    let e = QsQuery::<Query>::from_request(&req, &mut pl).unwrap_err(); +    assert_eq!( +        e.as_response_error().error_response().status(), +        StatusCode::BAD_REQUEST +    ); +} + +#[test] +fn test_custom_error_handler() { +    let req = TestRequest::with_uri("/test") +        .data(QsQueryConfig::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 = QsQuery::<Query>::from_request(&req, &mut pl); + +    assert!(query.is_err()); +    assert_eq!( +        query +            .unwrap_err() +            .as_response_error() +            .error_response() +            .status(), +        StatusCode::UNPROCESSABLE_ENTITY +    ); +} + +#[test] +fn test_composite_querystring_extractor() { +    let req = TestRequest::with_uri( +        "/test?foo=1&bars[]=0&bars[]=1&limit=100&offset=50&remaining=true", +    ) +    .to_srv_request(); +    let (req, mut pl) = req.into_parts(); + +    let s = QsQuery::<Query>::from_request(&req, &mut pl).unwrap(); +    assert_eq!(s.foo, 1); +    assert_eq!(s.bars, vec![0, 1]); +    assert_eq!(s.common.limit, 100); +    assert_eq!(s.common.offset, 50); +    assert_eq!(s.common.remaining, true); +} | 
