summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/actix.rs156
-rw-r--r--src/axum.rs88
-rw-r--r--src/de/mod.rs16
-rw-r--r--src/de/parse.rs4
-rw-r--r--src/lib.rs11
-rw-r--r--src/ser.rs67
6 files changed, 261 insertions, 81 deletions
diff --git a/src/actix.rs b/src/actix.rs
index a185212..02e9ae8 100644
--- a/src/actix.rs
+++ b/src/actix.rs
@@ -5,25 +5,25 @@
use crate::de::Config as QsConfig;
use crate::error::Error as QsError;
-#[cfg(feature = "actix2")]
-use actix_web2 as actix_web;
#[cfg(feature = "actix3")]
use actix_web3 as actix_web;
#[cfg(feature = "actix4")]
use actix_web4 as actix_web;
use actix_web::dev::Payload;
-#[cfg(any(feature = "actix2", feature = "actix3"))]
+#[cfg(feature = "actix3")]
use actix_web::HttpResponse;
-use actix_web::{Error as ActixError, FromRequest, HttpRequest, ResponseError};
-use futures::future::{ready, Ready};
+use actix_web::{web, Error as ActixError, FromRequest, HttpRequest, ResponseError};
+use futures::future::{ready, FutureExt, LocalBoxFuture, Ready};
+use futures::StreamExt;
use serde::de;
+use serde::de::DeserializeOwned;
use std::fmt;
use std::fmt::{Debug, Display};
use std::ops::{Deref, DerefMut};
use std::sync::Arc;
-#[cfg(any(feature = "actix2", feature = "actix3"))]
+#[cfg(feature = "actix3")]
impl ResponseError for QsError {
fn error_response(&self) -> HttpResponse {
HttpResponse::BadRequest().finish()
@@ -48,8 +48,6 @@ impl ResponseError for QsError {
/// # use actix_web4 as actix_web;
/// # #[cfg(feature = "actix3")]
/// # use actix_web3 as actix_web;
-/// # #[cfg(feature = "actix2")]
-/// # use actix_web2 as actix_web;
/// use actix_web::{web, App, HttpResponse};
/// use serde_qs::actix::QsQuery;
///
@@ -74,6 +72,12 @@ impl ResponseError for QsError {
/// ```
pub struct QsQuery<T>(T);
+impl<T> QsQuery<T> {
+ /// Unwrap into inner T value
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
impl<T> Deref for QsQuery<T> {
type Target = T;
@@ -88,13 +92,6 @@ impl<T> DerefMut for QsQuery<T> {
}
}
-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)
@@ -113,25 +110,19 @@ where
{
type Error = ActixError;
type Future = Ready<Result<Self, ActixError>>;
- #[cfg(any(feature = "actix2", feature = "actix3"))]
+ #[cfg(feature = "actix3")]
type Config = QsQueryConfig;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
- let query_config = req.app_data::<QsQueryConfig>();
-
- let error_handler = query_config.map(|c| c.ehandler.clone()).unwrap_or(None);
+ let query_config = req.app_data::<QsQueryConfig>().unwrap_or(&DEFAULT_CONFIG);
- let default_qsconfig = QsConfig::default();
- let qsconfig = query_config
- .map(|c| &c.qs_config)
- .unwrap_or(&default_qsconfig);
-
- let res = qsconfig
+ let res = query_config
+ .qs_config
.deserialize_str::<T>(req.query_string())
.map(|val| Ok(QsQuery(val)))
.unwrap_or_else(move |e| {
- let e = if let Some(error_handler) = error_handler {
+ let e = if let Some(error_handler) = &query_config.ehandler {
(error_handler)(e, req)
} else {
e.into()
@@ -143,6 +134,8 @@ where
}
}
+type ActixErrorHandler = Option<Arc<dyn Fn(QsError, &HttpRequest) -> ActixError + Send + Sync>>;
+
/// Query extractor configuration
///
/// ```rust
@@ -151,8 +144,6 @@ where
/// # use actix_web4 as actix_web;
/// # #[cfg(feature = "actix3")]
/// # use actix_web3 as actix_web;
-/// # #[cfg(feature = "actix2")]
-/// # use actix_web2 as actix_web;
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_qs::actix::QsQuery;
/// use serde_qs::Config as QsConfig;
@@ -184,11 +175,17 @@ where
/// );
/// }
/// ```
+#[derive(Clone, Default)]
pub struct QsQueryConfig {
- ehandler: Option<Arc<dyn Fn(QsError, &HttpRequest) -> ActixError + Send + Sync>>,
+ ehandler: ActixErrorHandler,
qs_config: QsConfig,
}
+static DEFAULT_CONFIG: QsQueryConfig = QsQueryConfig {
+ ehandler: None,
+ qs_config: crate::de::DEFAULT_CONFIG,
+};
+
impl QsQueryConfig {
/// Set custom error handler
pub fn error_handler<F>(mut self, f: F) -> Self
@@ -206,11 +203,102 @@ impl QsQueryConfig {
}
}
-impl Default for QsQueryConfig {
- fn default() -> Self {
- QsQueryConfig {
- ehandler: None,
- qs_config: QsConfig::default(),
+#[derive(PartialEq, Eq, PartialOrd, Ord)]
+/// Extract typed information from from the request's form data.
+///
+/// ## Example
+///
+/// ```rust
+/// # #[macro_use] extern crate serde_derive;
+/// # #[cfg(feature = "actix4")]
+/// # use actix_web4 as actix_web;
+/// # #[cfg(feature = "actix3")]
+/// # use actix_web3 as actix_web;
+/// use actix_web::{web, App, HttpResponse};
+/// use serde_qs::actix::QsForm;
+///
+/// #[derive(Debug, Deserialize)]
+/// pub struct UsersFilter {
+/// id: Vec<u64>,
+/// }
+///
+/// // Use `QsForm` extractor for Form information.
+/// // Content-Type: application/x-www-form-urlencoded
+/// // The correct request payload for this handler would be `id[]=1124&id[]=88`
+/// async fn filter_users(info: QsForm<UsersFilter>) -> HttpResponse {
+/// HttpResponse::Ok().body(
+/// info.id.iter().map(|i| i.to_string()).collect::<Vec<String>>().join(", ")
+/// )
+/// }
+///
+/// fn main() {
+/// let app = App::new().service(
+/// web::resource("/users")
+/// .route(web::get().to(filter_users)));
+/// }
+/// ```
+#[derive(Debug)]
+pub struct QsForm<T>(T);
+
+impl<T> QsForm<T> {
+ /// Unwrap into inner T value
+ pub fn into_inner(self) -> T {
+ self.0
+ }
+}
+
+impl<T> Deref for QsForm<T> {
+ type Target = T;
+
+ fn deref(&self) -> &T {
+ &self.0
+ }
+}
+
+impl<T> DerefMut for QsForm<T> {
+ fn deref_mut(&mut self) -> &mut T {
+ &mut self.0
+ }
+}
+
+impl<T> FromRequest for QsForm<T>
+where
+ T: DeserializeOwned + Debug,
+{
+ type Error = ActixError;
+ type Future = LocalBoxFuture<'static, Result<Self, ActixError>>;
+ #[cfg(feature = "actix3")]
+ type Config = QsQueryConfig;
+
+ fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future {
+ let mut stream = payload.take();
+ let req_clone = req.clone();
+
+ let query_config: QsQueryConfig = req
+ .app_data::<QsQueryConfig>()
+ .unwrap_or(&DEFAULT_CONFIG)
+ .clone();
+ async move {
+ let mut bytes = web::BytesMut::new();
+
+ while let Some(item) = stream.next().await {
+ bytes.extend_from_slice(&item.unwrap());
+ }
+
+ query_config
+ .qs_config
+ .deserialize_bytes::<T>(&bytes)
+ .map(|val| Ok(QsForm(val)))
+ .unwrap_or_else(|e| {
+ let e = if let Some(error_handler) = &query_config.ehandler {
+ (error_handler)(e, &req_clone)
+ } else {
+ e.into()
+ };
+
+ Err(e)
+ })
}
+ .boxed_local()
}
}
diff --git a/src/axum.rs b/src/axum.rs
index bba133c..3b603f3 100644
--- a/src/axum.rs
+++ b/src/axum.rs
@@ -43,7 +43,7 @@ use axum::{
/// }
///
/// fn main() {
-/// let app = Router::<(), Body>::new()
+/// let app = Router::<()>::new()
/// .route("/users", get(filter_users));
/// }
pub struct QsQuery<T>(pub T);
@@ -68,7 +68,6 @@ impl<T: std::fmt::Debug> std::fmt::Debug for QsQuery<T> {
}
}
-#[axum::async_trait]
impl<T, S> FromRequestParts<S> for QsQuery<T>
where
T: serde::de::DeserializeOwned,
@@ -96,6 +95,89 @@ where
}
}
+/// Extractor that differentiates between the absence and presence of the query string
+/// using `Option<T>`. Absence of query string encoded as `None`. Otherwise, it behaves
+/// identical to the `QsQuery`.
+///
+/// ## Example
+///
+/// ```rust
+/// # extern crate axum_framework as axum;
+/// use serde_qs::axum::OptionalQsQuery;
+/// use serde_qs::Config;
+/// use axum::{response::IntoResponse, routing::get, Router, body::Body};
+///
+/// #[derive(serde::Deserialize)]
+/// pub struct UsersFilter {
+/// id: Vec<u64>,
+/// }
+///
+/// async fn filter_users(
+/// OptionalQsQuery(info): OptionalQsQuery<UsersFilter>
+/// ) -> impl IntoResponse {
+/// match info {
+/// Some(info) => todo!("Select users based on query string"),
+/// None => { todo!("No query string provided")}
+/// }
+/// }
+///
+/// fn main() {
+/// let app = Router::<()>::new()
+/// .route("/users", get(filter_users));
+/// }
+#[derive(Clone, Copy, Default)]
+pub struct OptionalQsQuery<T>(pub Option<T>);
+
+impl<T> std::ops::Deref for OptionalQsQuery<T> {
+ type Target = Option<T>;
+
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
+
+impl<T> std::ops::DerefMut for OptionalQsQuery<T> {
+ fn deref_mut(&mut self) -> &mut Self::Target {
+ &mut self.0
+ }
+}
+
+impl<T: std::fmt::Debug> std::fmt::Debug for OptionalQsQuery<T> {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ self.0.fmt(f)
+ }
+}
+
+impl<T, S> FromRequestParts<S> for OptionalQsQuery<T>
+where
+ T: serde::de::DeserializeOwned,
+ S: Send + Sync,
+{
+ type Rejection = QsQueryRejection;
+
+ async fn from_request_parts(
+ parts: &mut axum::http::request::Parts,
+ state: &S,
+ ) -> Result<Self, Self::Rejection> {
+ let Extension(qs_config) = Extension::<QsQueryConfig>::from_request_parts(parts, state)
+ .await
+ .unwrap_or_else(|_| Extension(QsQueryConfig::default()));
+ if let Some(query) = parts.uri.query() {
+ let error_handler = qs_config.error_handler.clone();
+ let config: QsConfig = qs_config.into();
+ config
+ .deserialize_str::<T>(query)
+ .map(|query| OptionalQsQuery(Some(query)))
+ .map_err(|err| match error_handler {
+ Some(handler) => handler(err),
+ None => QsQueryRejection::new(err, StatusCode::BAD_REQUEST),
+ })
+ } else {
+ Ok(OptionalQsQuery(None))
+ }
+ }
+}
+
#[derive(Debug)]
/// Rejection type for extractors that deserialize query strings
pub struct QsQueryRejection {
@@ -178,7 +260,7 @@ impl std::error::Error for QsQueryRejection {
/// }
///
/// fn main() {
-/// let app = Router::<(), Body>::new()
+/// let app = Router::<()>::new()
/// .route("/users", get(filter_users))
/// .layer(Extension(QsQueryConfig::new(5, false)
/// .error_handler(|err| {
diff --git a/src/de/mod.rs b/src/de/mod.rs
index 150f6fa..969351e 100644
--- a/src/de/mod.rs
+++ b/src/de/mod.rs
@@ -81,6 +81,7 @@ use std::iter::Peekable;
/// assert_eq!(map.get("a").unwrap().get("b").unwrap().get("c").unwrap(), "1");
/// ```
///
+#[derive(Clone, Copy)]
pub struct Config {
/// Specifies the maximum depth key that `serde_qs` will attempt to
/// deserialize. Default is 5.
@@ -89,9 +90,14 @@ pub struct Config {
strict: bool,
}
+pub const DEFAULT_CONFIG: Config = Config {
+ max_depth: 5,
+ strict: true,
+};
+
impl Default for Config {
fn default() -> Self {
- Self::new(5, true)
+ DEFAULT_CONFIG
}
}
@@ -190,7 +196,7 @@ pub fn from_str<'de, T: de::Deserialize<'de>>(input: &'de str) -> Result<T> {
/// A deserializer for the querystring format.
///
/// Supported top-level outputs are structs and maps.
-pub(crate) struct QsDeserializer<'a> {
+pub struct QsDeserializer<'a> {
iter: Peekable<IntoIter<Cow<'a, str>, Level<'a>>>,
value: Option<Level<'a>>,
}
@@ -214,9 +220,13 @@ impl<'a> QsDeserializer<'a> {
}
/// Returns a new `QsDeserializer<'a>`.
- fn with_config(config: &Config, input: &'a [u8]) -> Result<Self> {
+ pub fn with_config(config: &Config, input: &'a [u8]) -> Result<Self> {
parse::Parser::new(input, config.max_depth(), config.strict).as_deserializer()
}
+
+ pub fn new(input: &'a [u8]) -> Result<Self> {
+ Self::with_config(&Config::default(), input)
+ }
}
impl<'de> de::Deserializer<'de> for QsDeserializer<'de> {
diff --git a/src/de/parse.rs b/src/de/parse.rs
index 0080d02..3be7aa2 100644
--- a/src/de/parse.rs
+++ b/src/de/parse.rs
@@ -184,7 +184,7 @@ impl<'a> Iterator for Parser<'a> {
}
}
-impl<'a> Parser<'a> {
+impl Parser<'_> {
#[inline]
fn peek(&mut self) -> Option<<Self as Iterator>::Item> {
if self.peeked.is_some() {
@@ -268,7 +268,7 @@ impl<'a> Parser<'a> {
Cow::Owned(owned) => Ok(Cow::Owned(owned)),
};
self.clear_acc();
- ret.map_err(Error::from)
+ ret
}
/// In some ways the main way to use a `Parser`, this runs the parsing step
diff --git a/src/lib.rs b/src/lib.rs
index 82ba41a..e816839 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -186,7 +186,7 @@
#[macro_use]
extern crate serde;
-#[cfg(any(feature = "actix4", feature = "actix3", feature = "actix2"))]
+#[cfg(any(feature = "actix4", feature = "actix3"))]
pub mod actix;
#[cfg(feature = "actix")]
@@ -201,15 +201,20 @@ serde_qs = { version = "0.9", features = ["actix4"] }
"#
);
+#[cfg(feature = "actix2")]
+compile_error!(
+ r#"The `actix2` feature was removed in v0.13 due to CI issues and minimal interest in continuing support"#
+);
+
mod de;
mod error;
mod ser;
pub(crate) mod utils;
#[doc(inline)]
-pub use de::Config;
-#[doc(inline)]
pub use de::{from_bytes, from_str};
+#[doc(inline)]
+pub use de::{Config, QsDeserializer as Deserializer};
pub use error::Error;
#[doc(inline)]
pub use ser::{to_string, to_writer, Serializer};
diff --git a/src/ser.rs b/src/ser.rs
index 607fa0d..5c21d92 100644
--- a/src/ser.rs
+++ b/src/ser.rs
@@ -279,7 +279,7 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
write!(
self.writer,
"{}{}={}",
- amp.then_some("&").unwrap_or_default(),
+ if amp { "&" } else { "" },
key,
percent_encode(value, QS_ENCODE_SET)
.map(replace_space)
@@ -294,16 +294,11 @@ impl<'a, W: 'a + Write> QsSerializer<'a, W> {
fn write_unit(&mut self) -> Result<()> {
let amp = !self.first.swap(false, Ordering::Relaxed);
if let Some(ref key) = self.key {
- write!(
- self.writer,
- "{}{}=",
- amp.then_some("&").unwrap_or_default(),
- key,
- )
- .map_err(Error::from)
+ write!(self.writer, "{}{}=", if amp { "&" } else { "" }, key,).map_err(Error::from)
+ } else if amp {
+ write!(self.writer, "&").map_err(Error::from)
} else {
- // For top level unit types
- write!(self.writer, "{}", amp.then_some("&").unwrap_or_default(),).map_err(Error::from)
+ Ok(())
}
}
@@ -463,12 +458,12 @@ pub struct QsSeq<'a, W: 'a + Write>(QsSerializer<'a, W>, usize);
#[doc(hidden)]
pub struct QsMap<'a, W: 'a + Write>(QsSerializer<'a, W>, Option<Cow<'a, str>>);
-impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> {
+impl<W: Write> ser::SerializeTuple for QsSeq<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let key = self.1.to_string();
self.1 += 1;
@@ -482,12 +477,12 @@ impl<'a, W: Write> ser::SerializeTuple for QsSeq<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeSeq for QsSeq<'a, W> {
+impl<W: Write> ser::SerializeSeq for QsSeq<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_element<T: ?Sized>(&mut self, value: &T) -> Result<()>
+ fn serialize_element<T>(&mut self, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
@@ -499,12 +494,12 @@ impl<'a, W: Write> ser::SerializeSeq for QsSeq<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeStruct for QsSerializer<'a, W> {
+impl<W: Write> ser::SerializeStruct for QsSerializer<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()>
+ fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(self);
serializer.extend_key(key);
@@ -515,13 +510,13 @@ impl<'a, W: Write> ser::SerializeStruct for QsSerializer<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeStructVariant for QsSerializer<'a, W> {
+impl<W: Write> ser::SerializeStructVariant for QsSerializer<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_field<T: ?Sized>(&mut self, key: &'static str, value: &T) -> Result<()>
+ fn serialize_field<T>(&mut self, key: &'static str, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(self);
serializer.extend_key(key);
@@ -533,13 +528,13 @@ impl<'a, W: Write> ser::SerializeStructVariant for QsSerializer<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeTupleVariant for QsSeq<'a, W> {
+impl<W: Write> ser::SerializeTupleVariant for QsSeq<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
+ fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
@@ -552,13 +547,13 @@ impl<'a, W: Write> ser::SerializeTupleVariant for QsSeq<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeTupleStruct for QsSeq<'a, W> {
+impl<W: Write> ser::SerializeTupleStruct for QsSeq<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_field<T: ?Sized>(&mut self, value: &T) -> Result<()>
+ fn serialize_field<T>(&mut self, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&self.1.to_string());
@@ -571,21 +566,21 @@ impl<'a, W: Write> ser::SerializeTupleStruct for QsSeq<'a, W> {
}
}
-impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> {
+impl<W: Write> ser::SerializeMap for QsMap<'_, W> {
type Ok = ();
type Error = Error;
- fn serialize_key<T: ?Sized>(&mut self, key: &T) -> Result<()>
+ fn serialize_key<T>(&mut self, key: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
self.1 = Some(Cow::from(key.serialize(StringSerializer)?));
Ok(())
}
- fn serialize_value<T: ?Sized>(&mut self, value: &T) -> Result<()>
+ fn serialize_value<T>(&mut self, value: &T) -> Result<()>
where
- T: ser::Serialize,
+ T: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(&mut self.0);
if let Some(ref key) = self.1 {
@@ -601,10 +596,10 @@ impl<'a, W: Write> ser::SerializeMap for QsMap<'a, W> {
Ok(())
}
- fn serialize_entry<K: ?Sized, V: ?Sized>(&mut self, key: &K, value: &V) -> Result<()>
+ fn serialize_entry<K, V>(&mut self, key: &K, value: &V) -> Result<()>
where
- K: ser::Serialize,
- V: ser::Serialize,
+ K: ser::Serialize + ?Sized,
+ V: ser::Serialize + ?Sized,
{
let mut serializer = QsSerializer::new_from_ref(&mut self.0);
serializer.extend_key(&key.serialize(StringSerializer)?);