summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordaddinuz <daddinuz@gmail.com>2019-05-15 14:04:06 +0200
committerdaddinuz <daddinuz@gmail.com>2019-05-15 14:04:06 +0200
commit8f57d8ea722c390c1f02082b8c3c01c2c87d400f (patch)
treef3f186fc7ea78d2ca93ee3910f254b4a25c35a90
parent10b3add38d8aa04dff789469449340ee7f817f32 (diff)
update actix support to v1
-rw-r--r--Cargo.toml2
-rw-r--r--src/actix.rs312
-rw-r--r--tests/test_actix.rs147
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<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);
+}