From 8f57d8ea722c390c1f02082b8c3c01c2c87d400f Mon Sep 17 00:00:00 2001 From: daddinuz Date: Wed, 15 May 2019 14:04:06 +0200 Subject: update actix support to v1 --- Cargo.toml | 2 +- src/actix.rs | 312 +++++++++++++++++++++++++++------------------------- tests/test_actix.rs | 147 ++++++++++++++++--------- 3 files changed, 258 insertions(+), 203 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index a28dc83..7ec4b8c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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, -///} -/// -/// // 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) -> 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); - -impl Deref for QsQuery { - type Target = T; - - fn deref(&self) -> &T { - &self.0 - } -} - -impl DerefMut for QsQuery { - fn deref_mut(&mut self) -> &mut T { - &mut self.0 - } -} - -impl QsQuery { - /// Deconstruct to a inner value - pub fn into_inner(self) -> T { - self.0 - } -} - -impl FromRequest for QsQuery -where - T: de::DeserializeOwned, -{ - type Config = QsQueryConfig; - type Result = Result; - - #[inline] - fn from_request(req: &HttpRequest, cfg: &Self::Config) -> Self::Result { - let req2 = req.clone(); - let err = Rc::clone(&cfg.ehandler); - super::from_str::(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) -> Result { -/// 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 { - ehandler: Rc) -> actix_web::Error>, -} -impl QsQueryConfig { - /// Set custom error handler - pub fn error_handler(&mut self, f: F) -> &mut Self - where - F: Fn(QsError, &HttpRequest) -> actix_web::Error + 'static, - { - self.ehandler = Rc::new(f); - self - } -} - -impl Default for QsQueryConfig { - fn default() -> Self { - QsQueryConfig { - ehandler: Rc::new(|_, _| actix_web::error::UrlencodedError::Parse.into()), - } - } -} - -impl fmt::Debug for QsQuery { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl fmt::Display for QsQuery { - 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, +/// } +/// +/// // Use `QsQuery` extractor for query information. +/// // The correct request for this handler would be `/users?id[]=1124&id[]=88"` +/// fn filter_users(info: QsQuery) -> String { +/// info.id.iter().map(|i| i.to_string()).collect::>().join(", ").into() +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/users") +/// .route(web::get().to(filter_users))); +/// } +/// ``` +pub struct QsQuery(T); + +impl Deref for QsQuery { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl DerefMut for QsQuery { + fn deref_mut(&mut self) -> &mut T { + &mut self.0 + } +} + +impl QsQuery { + /// Deconstruct to a inner value + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Debug for QsQuery { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Display for QsQuery { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + self.0.fmt(f) + } +} + +impl FromRequest for QsQuery +where + T: de::DeserializeOwned, +{ + type Error = ActixError; + type Future = Result; + type Config = QsQueryConfig; + + #[inline] + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + let error_handler = req + .app_data::() + .map(|c| c.ehandler.clone()) + .unwrap_or(None); + + super::from_str::(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) -> String { +/// format!("Welcome {}!", info.username) +/// } +/// +/// fn main() { +/// let app = App::new().service( +/// web::resource("/index.html").data( +/// // change query extractor configuration +/// QsQuery::::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 ActixError + Send + Sync>>, +} + +impl QsQueryConfig { + /// Set custom error handler + pub fn error_handler(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 - 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, - #[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) -> 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 +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, + #[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::::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::::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::::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); +} -- cgit v1.2.3 From cbdc5e1a62986f7157a2badbb60c3da2b515a43a Mon Sep 17 00:00:00 2001 From: daddinuz Date: Mon, 10 Jun 2019 19:05:18 +0200 Subject: update actix version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7ec4b8c..69b0904 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ version = "0.4.6" repository = "samscott89/serde_qs" [dependencies] -actix-web = { version ="1.0.0-beta", optional = true } +actix-web = { version ="1.0.0", optional = true } data-encoding = "2.1.2" error-chain = "0.12.0" percent-encoding = "1.0.1" -- cgit v1.2.3