1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-12-01 02:54:36 +01:00
actix-web/src/types/query.rs

300 lines
8.3 KiB
Rust
Raw Normal View History

//! Query extractor
use std::sync::Arc;
use std::{fmt, ops};
use actix_http::error::Error;
2019-11-20 18:33:22 +01:00
use futures::future::{err, ok, Ready};
use serde::de;
use serde_urlencoded;
2019-04-07 23:43:07 +02:00
use crate::dev::Payload;
2019-05-15 19:31:40 +02:00
use crate::error::QueryPayloadError;
use crate::extract::FromRequest;
2019-04-07 23:43:07 +02:00
use crate::request::HttpRequest;
2019-07-05 16:46:55 +02:00
/// Extract typed information from the request's query.
///
2019-07-19 13:37:49 +02:00
/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot
/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs.
/// Attempts to do so will *fail at runtime*.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub enum ResponseType {
/// Token,
/// Code
/// }
///
/// #[derive(Deserialize)]
/// pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
/// }
///
2019-07-19 13:37:49 +02:00
/// // Use `Query` extractor for query information (and destructure it within the signature).
/// // This handler gets called only if the request's query string contains a `username` field.
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`.
2019-07-19 11:47:44 +02:00
/// fn index(web::Query(info): web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor
/// }
/// ```
2019-07-19 13:37:49 +02:00
#[derive(PartialEq, Eq, PartialOrd, Ord)]
2019-07-19 11:47:44 +02:00
pub struct Query<T>(pub T);
impl<T> Query<T> {
/// Deconstruct to a inner value
pub fn into_inner(self) -> T {
self.0
}
2019-05-17 22:10:46 +02:00
/// Get query parameters from the path
pub fn from_query(query_str: &str) -> Result<Self, QueryPayloadError>
where
T: de::DeserializeOwned,
{
serde_urlencoded::from_str::<T>(query_str)
.map(|val| Ok(Query(val)))
.unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e)))
}
}
impl<T> ops::Deref for Query<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
impl<T> ops::DerefMut for Query<T> {
fn deref_mut(&mut self) -> &mut T {
&mut self.0
}
}
impl<T: fmt::Debug> fmt::Debug for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
impl<T: fmt::Display> fmt::Display for Query<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.0.fmt(f)
}
}
2019-07-05 16:46:55 +02:00
/// Extract typed information from the request's query.
///
/// ## Example
///
/// ```rust
/// use actix_web::{web, App};
/// use serde_derive::Deserialize;
///
/// #[derive(Debug, Deserialize)]
/// pub enum ResponseType {
/// Token,
/// Code
/// }
///
/// #[derive(Deserialize)]
/// pub struct AuthRequest {
/// id: u64,
/// response_type: ResponseType,
/// }
///
/// // Use `Query` extractor for query information.
/// // This handler get called only if request's query contains `username` field
/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`
/// fn index(info: web::Query<AuthRequest>) -> String {
/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html")
/// .route(web::get().to(index))); // <- use `Query` extractor
/// }
/// ```
impl<T> FromRequest for Query<T>
where
T: de::DeserializeOwned,
{
type Error = Error;
2019-11-20 18:33:22 +01:00
type Future = Ready<Result<Self, Error>>;
type Config = QueryConfig;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
let error_handler = req
.app_data::<Self::Config>()
.map(|c| c.ehandler.clone())
.unwrap_or(None);
2019-04-07 23:43:07 +02:00
serde_urlencoded::from_str::<T>(req.query_string())
2019-11-20 18:33:22 +01:00
.map(|val| ok(Query(val)))
.unwrap_or_else(move |e| {
let e = QueryPayloadError::Deserialize(e);
2019-04-11 00:05:03 +02:00
log::debug!(
"Failed during Query extractor deserialization. \
Request path: {:?}",
req.path()
);
let e = if let Some(error_handler) = error_handler {
(error_handler)(e, req)
} else {
e.into()
};
2019-11-20 18:33:22 +01:00
err(e)
})
}
}
2019-04-18 20:01:04 +02:00
/// Query extractor configuration
///
2019-07-19 13:37:49 +02:00
/// ## Example
///
/// ```rust
/// use actix_web::{error, web, App, FromRequest, HttpResponse};
/// use serde_derive::Deserialize;
///
/// #[derive(Deserialize)]
/// struct Info {
/// username: String,
/// }
///
/// /// deserialize `Info` from request's querystring
/// fn index(info: web::Query<Info>) -> String {
/// format!("Welcome {}!", info.username)
/// }
///
/// fn main() {
/// let app = App::new().service(
/// web::resource("/index.html").data(
/// // change query extractor configuration
/// web::Query::<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))
/// );
/// }
/// ```
#[derive(Clone)]
pub struct QueryConfig {
2019-07-17 11:48:37 +02:00
ehandler:
Option<Arc<dyn Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync>>,
}
impl QueryConfig {
/// Set custom error handler
pub fn error_handler<F>(mut self, f: F) -> Self
2019-05-15 19:31:40 +02:00
where
F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static,
{
self.ehandler = Some(Arc::new(f));
self
}
}
impl Default for QueryConfig {
fn default() -> Self {
2019-05-15 19:31:40 +02:00
QueryConfig { ehandler: None }
}
}
2019-04-18 20:01:04 +02:00
#[cfg(test)]
mod tests {
2019-05-15 19:31:40 +02:00
use actix_http::http::StatusCode;
2019-04-18 20:01:04 +02:00
use derive_more::Display;
use serde_derive::Deserialize;
use super::*;
use crate::error::InternalError;
2019-11-20 18:33:22 +01:00
use crate::test::{block_on, TestRequest};
use crate::HttpResponse;
2019-04-18 20:01:04 +02:00
#[derive(Deserialize, Debug, Display)]
struct Id {
id: String,
}
2019-05-17 22:10:46 +02:00
#[test]
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, Id { id: \"test\" }");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
}
2019-04-18 20:01:04 +02:00
#[test]
fn test_request_extract() {
2019-11-20 18:33:22 +01:00
block_on(async {
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, Id { id: \"test\" }");
s.id = "test1".to_string();
let s = s.into_inner();
assert_eq!(s.id, "test1");
})
2019-04-18 20:01:04 +02:00
}
#[test]
fn test_custom_error_responder() {
2019-11-20 18:33:22 +01:00
block_on(async {
let req = TestRequest::with_uri("/name/user1/")
.data(QueryConfig::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 = Query::<Id>::from_request(&req, &mut pl).await;
assert!(query.is_err());
assert_eq!(
query
.unwrap_err()
.as_response_error()
.error_response()
.status(),
StatusCode::UNPROCESSABLE_ENTITY
);
})
}
2019-04-18 20:01:04 +02:00
}