1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190
//! For query parameter extractor documentation, see [`Query`].
use std::future::{ready, Ready};
use actix_web::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest};
use serde::de::DeserializeOwned;
use tracing::debug;
/// Extract typed information from the request's query.
///
/// To extract typed data from the URL query string, the inner type `T` must implement the
/// [`DeserializeOwned`] trait.
///
/// # Differences From `actix_web::web::Query`
/// This extractor uses `serde_html_form` under-the-hood which supports multi-value items. These are
/// sent by HTML select inputs when multiple options are chosen and can be collected into a `Vec`.
///
/// This version also removes the custom error handler config; users should instead prefer to handle
/// errors using the explicit `Result<Query<T>, E>` extractor in their handlers.
///
/// # Panics
/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any
/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
///
/// # Examples
/// ```
/// use actix_web::{get, Responder};
/// use actix_web_lab::extract::Query;
/// use serde::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// #[serde(rename_all = "lowercase")]
/// enum LogType {
/// Reports,
/// Actions,
/// }
///
/// #[derive(Debug, Deserialize)]
/// pub struct LogsParams {
/// #[serde(rename = "type")]
/// log_type: u64,
///
/// #[serde(rename = "user")]
/// users: Vec<String>,
/// }
///
/// // Deserialize `LogsParams` struct from query string.
/// // This handler gets called only if the request's query parameters contain both fields.
/// // A valid request path for this handler would be `/logs?type=reports&user=foo&user=bar"`.
/// #[get("/logs")]
/// async fn index(info: Query<LogsParams>) -> impl Responder {
/// let LogsParams { log_type, users } = info.into_inner();
/// format!("Logs request for type={log_type} and user list={users:?}!")
/// }
///
/// // Or use destructuring, which is equivalent to `.into_inner()`.
/// #[get("/debug2")]
/// async fn debug2(Query(info): Query<LogsParams>) -> impl Responder {
/// dbg!("Authorization object = {info:?}");
/// "OK"
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Query<T>(pub T);
impl_more::impl_deref_and_mut!(<T> in Query<T> => T);
impl_more::forward_display!(<T> in Query<T>);
impl<T> Query<T> {
/// Unwrap into inner `T` value.
pub fn into_inner(self) -> T {
self.0
}
}
impl<T: DeserializeOwned> Query<T> {
/// Deserialize a `T` from the URL encoded query parameter string.
///
/// ```
/// # use std::collections::HashMap;
/// # use actix_web_lab::extract::Query;
/// let numbers = Query::<HashMap<String, u32>>::from_query("one=1&two=2").unwrap();
///
/// assert_eq!(numbers.get("one"), Some(&1));
/// assert_eq!(numbers.get("two"), Some(&2));
/// assert!(numbers.get("three").is_none());
/// ```
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError> {
serde_html_form::from_str::<T>(query_str)
.map(Self)
.map_err(QueryPayloadError::Deserialize)
}
}
/// See [here](#examples) for example of usage as an extractor.
impl<T: DeserializeOwned> FromRequest for Query<T> {
type Error = Error;
type Future = Ready<Result<Self, Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
serde_html_form::from_str::<T>(req.query_string())
.map(|val| ready(Ok(Query(val))))
.unwrap_or_else(move |e| {
let err = QueryPayloadError::Deserialize(e);
debug!(
"Failed during Query extractor deserialization. \
Request path: {:?}",
req.path()
);
ready(Err(err.into()))
})
}
}
#[cfg(test)]
mod tests {
use actix_web::test::TestRequest;
use derive_more::Display;
use serde::Deserialize;
use super::*;
#[derive(Deserialize, Debug, Display)]
struct Id {
id: String,
}
#[actix_web::test]
async fn test_service_request_extract() {
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
assert!(Query::<Id>::from_query(req.query_string()).is_err());
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let mut s = Query::<Id>::from_query(req.query_string()).unwrap();
assert_eq!(s.id, "test");
assert_eq!(format!("{s}, {s:?}"), "test, Query(Id { id: \"test\" })");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
}
#[actix_web::test]
async fn extract_array() {
#[derive(Debug, Deserialize)]
struct Test {
#[serde(rename = "user")]
users: Vec<String>,
}
let req = TestRequest::with_uri("/?user=foo&user=bar").to_srv_request();
let s = Query::<Test>::from_query(req.query_string()).unwrap();
assert_eq!(s.users[0], "foo");
assert_eq!(s.users[1], "bar");
}
#[actix_web::test]
async fn test_request_extract() {
let req = TestRequest::with_uri("/name/user1/").to_srv_request();
let (req, mut pl) = req.into_parts();
assert!(Query::<Id>::from_request(&req, &mut pl).await.is_err());
let req = TestRequest::with_uri("/name/user1/?id=test").to_srv_request();
let (req, mut pl) = req.into_parts();
let mut s = Query::<Id>::from_request(&req, &mut pl).await.unwrap();
assert_eq!(s.id, "test");
assert_eq!(format!("{s}, {s:?}"), "test, Query(Id { id: \"test\" })");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
}
#[actix_web::test]
#[should_panic]
async fn test_tuple_panic() {
let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request();
let (req, mut pl) = req.into_parts();
Query::<(u32, u32)>::from_request(&req, &mut pl)
.await
.unwrap();
}
}