mirror of
https://github.com/actix/actix-extras.git
synced 2024-11-24 07:53:00 +01:00
add path and query extractors
This commit is contained in:
parent
a56e5113ee
commit
68cf32e848
@ -62,6 +62,7 @@ rand = "0.4"
|
|||||||
regex = "0.2"
|
regex = "0.2"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
|
serde_urlencoded = "0.5"
|
||||||
sha1 = "0.6"
|
sha1 = "0.6"
|
||||||
smallvec = "0.6"
|
smallvec = "0.6"
|
||||||
time = "0.1"
|
time = "0.1"
|
||||||
|
231
src/extractor.rs
Normal file
231
src/extractor.rs
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
use serde_urlencoded;
|
||||||
|
use serde::de::{self, Deserializer, Visitor, Error as DeError};
|
||||||
|
|
||||||
|
use error::{Error, ErrorBadRequest};
|
||||||
|
use httprequest::HttpRequest;
|
||||||
|
|
||||||
|
pub trait HttpRequestExtractor<'de> {
|
||||||
|
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, Error>
|
||||||
|
where T: de::Deserialize<'de>, S: 'static;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract typed information from the request's path.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<String> {
|
||||||
|
/// let info: Info = req.extract(Path)?; // <- extract path info using serde
|
||||||
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .resource("/{username}/index.html", // <- define path parameters
|
||||||
|
/// |r| r.method(Method::GET).f(index));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub struct Path;
|
||||||
|
|
||||||
|
impl<'de> HttpRequestExtractor<'de> for Path {
|
||||||
|
#[inline]
|
||||||
|
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, Error>
|
||||||
|
where T: de::Deserialize<'de>, S: 'static,
|
||||||
|
{
|
||||||
|
Ok(de::Deserialize::deserialize(PathExtractor{req: req})
|
||||||
|
.map_err(ErrorBadRequest)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extract typed information from from the request's query.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<String> {
|
||||||
|
/// let info: Info = req.extract(Query)?; // <- extract query info using serde
|
||||||
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub struct Query;
|
||||||
|
|
||||||
|
impl<'de> HttpRequestExtractor<'de> for Query {
|
||||||
|
#[inline]
|
||||||
|
fn extract<T, S>(&self, req: &'de HttpRequest<S>) -> Result<T, Error>
|
||||||
|
where T: de::Deserialize<'de>, S: 'static,
|
||||||
|
{
|
||||||
|
Ok(serde_urlencoded::from_str::<T>(req.query_string())
|
||||||
|
.map_err(ErrorBadRequest)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! unsupported_type {
|
||||||
|
($trait_fn:ident, $name:expr) => {
|
||||||
|
fn $trait_fn<V>(self, _: V) -> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>
|
||||||
|
{
|
||||||
|
Err(de::value::Error::custom(concat!("unsupported type: ", $name)))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PathExtractor<'de, S: 'static> {
|
||||||
|
req: &'de HttpRequest<S>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de, S: 'static> Deserializer<'de> for PathExtractor<'de, S>
|
||||||
|
{
|
||||||
|
type Error = de::value::Error;
|
||||||
|
|
||||||
|
fn deserialize_map<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_map(de::value::MapDeserializer::new(
|
||||||
|
self.req.match_info().iter().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref()))))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_struct<V>(self, _: &'static str, _: &'static [&'static str], visitor: V)
|
||||||
|
-> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
self.deserialize_map(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_unit<V>(self, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where
|
||||||
|
V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_unit()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_unit_struct<V>(self, _: &'static str, visitor: V)
|
||||||
|
-> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>
|
||||||
|
{
|
||||||
|
self.deserialize_unit(visitor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_newtype_struct<V>(self, _: &'static str, visitor: V)
|
||||||
|
-> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>,
|
||||||
|
{
|
||||||
|
visitor.visit_newtype_struct(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_tuple<V>(self, len: usize, visitor: V) -> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>
|
||||||
|
{
|
||||||
|
if self.req.match_info().len() < len {
|
||||||
|
Err(de::value::Error::custom(
|
||||||
|
format!("wrong number of parameters: {} expected {}",
|
||||||
|
self.req.match_info().len(), len).as_str()))
|
||||||
|
} else {
|
||||||
|
visitor.visit_seq(de::value::SeqDeserializer::new(
|
||||||
|
self.req.match_info().iter().map(|&(_, ref v)| v.as_ref())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_tuple_struct<V>(self, _: &'static str, _: usize, visitor: V)
|
||||||
|
-> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>
|
||||||
|
{
|
||||||
|
visitor.visit_seq(de::value::SeqDeserializer::new(
|
||||||
|
self.req.match_info().iter().map(|&(_, ref v)| v.as_ref())))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize_enum<V>(self, _: &'static str, _: &'static [&'static str], _: V)
|
||||||
|
-> Result<V::Value, Self::Error>
|
||||||
|
where V: Visitor<'de>
|
||||||
|
{
|
||||||
|
Err(de::value::Error::custom("unsupported type: enum"))
|
||||||
|
}
|
||||||
|
|
||||||
|
unsupported_type!(deserialize_any, "'any'");
|
||||||
|
unsupported_type!(deserialize_bool, "bool");
|
||||||
|
unsupported_type!(deserialize_i8, "i8");
|
||||||
|
unsupported_type!(deserialize_i16, "i16");
|
||||||
|
unsupported_type!(deserialize_i32, "i32");
|
||||||
|
unsupported_type!(deserialize_i64, "i64");
|
||||||
|
unsupported_type!(deserialize_u8, "u8");
|
||||||
|
unsupported_type!(deserialize_u16, "u16");
|
||||||
|
unsupported_type!(deserialize_u32, "u32");
|
||||||
|
unsupported_type!(deserialize_u64, "u64");
|
||||||
|
unsupported_type!(deserialize_f32, "f32");
|
||||||
|
unsupported_type!(deserialize_f64, "f64");
|
||||||
|
unsupported_type!(deserialize_char, "char");
|
||||||
|
unsupported_type!(deserialize_str, "str");
|
||||||
|
unsupported_type!(deserialize_string, "String");
|
||||||
|
unsupported_type!(deserialize_bytes, "bytes");
|
||||||
|
unsupported_type!(deserialize_byte_buf, "byte buf");
|
||||||
|
unsupported_type!(deserialize_option, "Option<T>");
|
||||||
|
unsupported_type!(deserialize_seq, "sequence");
|
||||||
|
unsupported_type!(deserialize_identifier, "identifier");
|
||||||
|
unsupported_type!(deserialize_ignored_any, "ignored_any");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use router::{Router, Pattern};
|
||||||
|
use resource::Resource;
|
||||||
|
use test::TestRequest;
|
||||||
|
use server::ServerSettings;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct MyStruct {
|
||||||
|
key: String,
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Id {
|
||||||
|
id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_request_extract() {
|
||||||
|
let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
|
||||||
|
|
||||||
|
let mut resource = Resource::<()>::default();
|
||||||
|
resource.name("index");
|
||||||
|
let mut routes = Vec::new();
|
||||||
|
routes.push((Pattern::new("index", "/{key}/{value}/"), Some(resource)));
|
||||||
|
let (router, _) = Router::new("", ServerSettings::default(), routes);
|
||||||
|
assert!(router.recognize(&mut req).is_some());
|
||||||
|
|
||||||
|
let s: MyStruct = req.extract(Path).unwrap();
|
||||||
|
assert_eq!(s.key, "name");
|
||||||
|
assert_eq!(s.value, "user1");
|
||||||
|
|
||||||
|
let s: (String, String) = req.extract(Path).unwrap();
|
||||||
|
assert_eq!(s.0, "name");
|
||||||
|
assert_eq!(s.1, "user1");
|
||||||
|
|
||||||
|
let s: Id = req.extract(Query).unwrap();
|
||||||
|
assert_eq!(s.id, "test");
|
||||||
|
}
|
||||||
|
}
|
@ -227,6 +227,9 @@ impl From<Box<Future<Item=HttpResponse, Error=Error>>> for Reply {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convenience type alias
|
||||||
|
pub type FutureResponse<I, E=Error> = Box<Future<Item=I, Error=E>>;
|
||||||
|
|
||||||
impl<I, E> Responder for Box<Future<Item=I, Error=E>>
|
impl<I, E> Responder for Box<Future<Item=I, Error=E>>
|
||||||
where I: Responder + 'static,
|
where I: Responder + 'static,
|
||||||
E: Into<Error> + 'static
|
E: Into<Error> + 'static
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
use std::fmt::{self, Display};
|
use std::fmt::{self, Display};
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::ascii::AsciiExt;
|
|
||||||
|
|
||||||
use self::Charset::*;
|
use self::Charset::*;
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ use failure;
|
|||||||
use url::{Url, form_urlencoded};
|
use url::{Url, form_urlencoded};
|
||||||
use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode};
|
use http::{header, Uri, Method, Version, HeaderMap, Extensions, StatusCode};
|
||||||
use tokio_io::AsyncRead;
|
use tokio_io::AsyncRead;
|
||||||
|
use serde::de;
|
||||||
|
|
||||||
use body::Body;
|
use body::Body;
|
||||||
use info::ConnectionInfo;
|
use info::ConnectionInfo;
|
||||||
@ -19,7 +20,8 @@ use payload::Payload;
|
|||||||
use httpmessage::HttpMessage;
|
use httpmessage::HttpMessage;
|
||||||
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
use httpresponse::{HttpResponse, HttpResponseBuilder};
|
||||||
use helpers::SharedHttpInnerMessage;
|
use helpers::SharedHttpInnerMessage;
|
||||||
use error::{UrlGenerationError, CookieParseError, PayloadError};
|
use extractor::HttpRequestExtractor;
|
||||||
|
use error::{Error, UrlGenerationError, CookieParseError, PayloadError};
|
||||||
|
|
||||||
|
|
||||||
pub struct HttpInnerMessage {
|
pub struct HttpInnerMessage {
|
||||||
@ -395,6 +397,42 @@ impl<S> HttpRequest<S> {
|
|||||||
unsafe{ mem::transmute(&mut self.as_mut().params) }
|
unsafe{ mem::transmute(&mut self.as_mut().params) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extract typed information from path.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
///
|
||||||
|
/// ```rust
|
||||||
|
/// # extern crate bytes;
|
||||||
|
/// # extern crate actix_web;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// #[macro_use] extern crate serde_derive;
|
||||||
|
/// use actix_web::*;
|
||||||
|
///
|
||||||
|
/// #[derive(Deserialize)]
|
||||||
|
/// struct Info {
|
||||||
|
/// username: String,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn index(mut req: HttpRequest) -> Result<String> {
|
||||||
|
/// let info: Info = req.extract(Path)?; // <- extract path info using serde
|
||||||
|
/// let info: Info = req.extract(Query)?; // <- extract query info
|
||||||
|
/// Ok(format!("Welcome {}!", info.username))
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn main() {
|
||||||
|
/// let app = Application::new()
|
||||||
|
/// .resource("/{username}/index.html", // <- define path parameters
|
||||||
|
/// |r| r.method(Method::GET).f(index));
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
pub fn extract<'a, T, D>(&'a self, ds: D) -> Result<T, Error>
|
||||||
|
where S: 'static,
|
||||||
|
T: de::Deserialize<'a>,
|
||||||
|
D: HttpRequestExtractor<'a>
|
||||||
|
{
|
||||||
|
ds.extract(self)
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if a connection should be kept alive.
|
/// Checks if a connection should be kept alive.
|
||||||
pub fn keep_alive(&self) -> bool {
|
pub fn keep_alive(&self) -> bool {
|
||||||
self.as_ref().keep_alive()
|
self.as_ref().keep_alive()
|
||||||
|
@ -78,6 +78,7 @@ extern crate url;
|
|||||||
extern crate libc;
|
extern crate libc;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
extern crate serde_urlencoded;
|
||||||
extern crate flate2;
|
extern crate flate2;
|
||||||
#[cfg(feature="brotli")]
|
#[cfg(feature="brotli")]
|
||||||
extern crate brotli2;
|
extern crate brotli2;
|
||||||
@ -118,6 +119,7 @@ mod resource;
|
|||||||
mod param;
|
mod param;
|
||||||
mod payload;
|
mod payload;
|
||||||
mod pipeline;
|
mod pipeline;
|
||||||
|
mod extractor;
|
||||||
|
|
||||||
pub mod client;
|
pub mod client;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
@ -137,11 +139,12 @@ pub use application::Application;
|
|||||||
pub use httpmessage::HttpMessage;
|
pub use httpmessage::HttpMessage;
|
||||||
pub use httprequest::HttpRequest;
|
pub use httprequest::HttpRequest;
|
||||||
pub use httpresponse::HttpResponse;
|
pub use httpresponse::HttpResponse;
|
||||||
pub use handler::{Either, Reply, Responder, NormalizePath, AsyncResponder};
|
pub use handler::{Either, Reply, Responder, NormalizePath, AsyncResponder, FutureResponse};
|
||||||
pub use route::Route;
|
pub use route::Route;
|
||||||
pub use resource::Resource;
|
pub use resource::Resource;
|
||||||
pub use context::HttpContext;
|
pub use context::HttpContext;
|
||||||
pub use server::HttpServer;
|
pub use server::HttpServer;
|
||||||
|
pub use extractor::{Path, Query};
|
||||||
|
|
||||||
// re-exports
|
// re-exports
|
||||||
pub use http::{Method, StatusCode, Version};
|
pub use http::{Method, StatusCode, Version};
|
||||||
@ -187,4 +190,5 @@ pub mod dev {
|
|||||||
pub use param::{FromParam, Params};
|
pub use param::{FromParam, Params};
|
||||||
pub use httpmessage::{UrlEncoded, MessageBody};
|
pub use httpmessage::{UrlEncoded, MessageBody};
|
||||||
pub use httpresponse::HttpResponseBuilder;
|
pub use httpresponse::HttpResponseBuilder;
|
||||||
|
pub use extractor::HttpRequestExtractor;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,11 @@ impl<'a> Params<'a> {
|
|||||||
self.0.is_empty()
|
self.0.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check number of extracted parameters
|
||||||
|
pub fn len(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
/// Get matched parameter by name without type conversion
|
/// Get matched parameter by name without type conversion
|
||||||
pub fn get(&'a self, key: &str) -> Option<&'a str> {
|
pub fn get(&'a self, key: &str) -> Option<&'a str> {
|
||||||
for item in self.0.iter() {
|
for item in self.0.iter() {
|
||||||
|
@ -302,6 +302,11 @@ impl<S> PayloadHelper<S> where S: Stream<Item=Bytes, Error=PayloadError> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get mutable reference to an inner stream.
|
||||||
|
pub fn get_mut(&mut self) -> &mut S {
|
||||||
|
&mut self.stream
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
|
fn poll_stream(&mut self) -> Poll<bool, PayloadError> {
|
||||||
self.stream.poll().map(|res| {
|
self.stream.poll().map(|res| {
|
||||||
|
@ -549,6 +549,7 @@ impl Reader {
|
|||||||
msg
|
msg
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc7230#section-3.3.3
|
||||||
let decoder = if has_te && chunked(&msg.get_mut().headers)? {
|
let decoder = if has_te && chunked(&msg.get_mut().headers)? {
|
||||||
// Chunked encoding
|
// Chunked encoding
|
||||||
Some(Decoder::chunked())
|
Some(Decoder::chunked())
|
||||||
|
Loading…
Reference in New Issue
Block a user