diff --git a/src/de.rs b/src/de.rs index a58277a97..8e8b501b2 100644 --- a/src/de.rs +++ b/src/de.rs @@ -1,3 +1,5 @@ +use std::slice::Iter; +use std::borrow::Cow; use std::ops::{Deref, DerefMut}; use serde_urlencoded; @@ -25,6 +27,32 @@ use httprequest::HttpRequest; /// /// Application state /// struct State {} /// +/// /// extract path info using serde +/// fn index(info: Path<(String, u32), State>) -> Result { +/// Ok(format!("Welcome {}! {}", info.0, info.1)) +/// } +/// +/// fn main() { +/// let app = Application::with_state(State{}).resource( +/// "/{username}/{count}/?index.html", // <- define path parameters +/// |r| r.method(Method::GET).with(index)); // <- use `with` extractor +/// } +/// ``` +/// +/// It is possible to extract path information to a specific type that implements +/// `Deserialize` trait from *serde*. +/// +/// ```rust +/// # extern crate bytes; +/// # extern crate actix_web; +/// # extern crate futures; +/// #[macro_use] extern crate serde_derive; +/// # use actix_web::*; +/// use actix_web::Path; +/// +/// /// Application state +/// struct State {} +/// /// #[derive(Deserialize)] /// struct Info { /// username: String, @@ -203,8 +231,10 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> fn deserialize_map(self, visitor: V) -> Result 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())))) + visitor.visit_map(ParamsDeserializer{ + params: self.req.match_info().iter(), + current: None, + }) } fn deserialize_struct(self, _: &'static str, _: &'static [&'static str], visitor: V) @@ -243,8 +273,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> 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()))) + visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()}) } } @@ -252,8 +281,7 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> -> Result where V: Visitor<'de> { - visitor.visit_seq(de::value::SeqDeserializer::new( - self.req.match_info().iter().map(|&(_, ref v)| v.as_ref()))) + visitor.visit_seq(ParamsSeq{params: self.req.match_info().iter()}) } fn deserialize_enum(self, _: &'static str, _: &'static [&'static str], _: V) @@ -286,6 +314,237 @@ impl<'de, S: 'de> Deserializer<'de> for PathDeserializer<'de, S> unsupported_type!(deserialize_ignored_any, "ignored_any"); } +struct ParamsDeserializer<'de> { + params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>, + current: Option<(&'de str, &'de str)>, +} + +impl<'de> de::MapAccess<'de> for ParamsDeserializer<'de> +{ + type Error = de::value::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where K: de::DeserializeSeed<'de>, + { + self.current = self.params.next().map(|&(ref k, ref v)| (k.as_ref(), v.as_ref())); + match self.current { + Some((key, _)) => Ok(Some(seed.deserialize(Key{key})?)), + None => Ok(None), + } + } + + fn next_value_seed(&mut self, seed: V) -> Result + where V: de::DeserializeSeed<'de>, + { + if let Some((_, value)) = self.current.take() { + seed.deserialize(Value { value }) + } else { + Err(de::value::Error::custom("unexpected item")) + } + } +} + +struct Key<'de> { + key: &'de str, +} + +impl<'de> Deserializer<'de> for Key<'de> { + type Error = de::value::Error; + + fn deserialize_identifier(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_str(self.key) + } + + fn deserialize_any(self, _visitor: V) -> Result + where V: Visitor<'de>, + { + Err(de::value::Error::custom("Unexpected")) + } + + forward_to_deserialize_any! { + bool i8 i16 i32 i64 u8 u16 u32 u64 f32 f64 char str string bytes + byte_buf option unit unit_struct newtype_struct seq tuple + tuple_struct map struct enum ignored_any + } +} + +macro_rules! parse_value { + ($trait_fn:ident, $visitor_fn:ident, $tp:tt) => { + fn $trait_fn(self, visitor: V) -> Result + where V: Visitor<'de> + { + let v = self.value.parse().map_err( + |_| de::value::Error::custom( + format!("can not parse {:?} to a {}", self.value, $tp)))?; + visitor.$visitor_fn(v) + } + } +} + +struct Value<'de> { + value: &'de str, +} + +impl<'de> Deserializer<'de> for Value<'de> +{ + type Error = de::value::Error; + + parse_value!(deserialize_bool, visit_bool, "bool"); + parse_value!(deserialize_i8, visit_i8, "i8"); + parse_value!(deserialize_i16, visit_i16, "i16"); + parse_value!(deserialize_i32, visit_i32, "i16"); + parse_value!(deserialize_i64, visit_i64, "i64"); + parse_value!(deserialize_u8, visit_u8, "u8"); + parse_value!(deserialize_u16, visit_u16, "u16"); + parse_value!(deserialize_u32, visit_u32, "u32"); + parse_value!(deserialize_u64, visit_u64, "u64"); + parse_value!(deserialize_f32, visit_f32, "f32"); + parse_value!(deserialize_f64, visit_f64, "f64"); + parse_value!(deserialize_string, visit_string, "String"); + parse_value!(deserialize_byte_buf, visit_string, "String"); + parse_value!(deserialize_char, visit_char, "char"); + + fn deserialize_ignored_any(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_unit() + } + + fn deserialize_unit_struct( + self, _: &'static str, visitor: V) -> Result + where V: Visitor<'de> + { + visitor.visit_unit() + } + + fn deserialize_bytes(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_borrowed_bytes(self.value.as_bytes()) + } + + fn deserialize_str(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_borrowed_str(self.value) + } + + fn deserialize_option(self, visitor: V) -> Result + where V: Visitor<'de>, + { + visitor.visit_some(self) + } + + fn deserialize_enum(self, _: &'static str, _: &'static [&'static str], visitor: V) + -> Result + where V: Visitor<'de>, + { + visitor.visit_enum(ValueEnum {value: self.value}) + } + + fn deserialize_newtype_struct(self, _: &'static str, visitor: V) + -> Result + where V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_tuple(self, _: usize, _: V) -> Result + where V: Visitor<'de> + { + Err(de::value::Error::custom("unsupported type: tuple")) + } + + fn deserialize_struct(self, _: &'static str, _: &'static [&'static str], _: V) + -> Result + where V: Visitor<'de> + { + Err(de::value::Error::custom("unsupported type: struct")) + } + + fn deserialize_tuple_struct(self, _: &'static str, _: usize, _: V) + -> Result + where V: Visitor<'de> + { + Err(de::value::Error::custom("unsupported type: tuple struct")) + } + + unsupported_type!(deserialize_any, ""); + unsupported_type!(deserialize_seq, "seq"); + unsupported_type!(deserialize_map, "map"); + unsupported_type!(deserialize_identifier, "identifier"); +} + +struct ParamsSeq<'de> { + params: Iter<'de, (Cow<'de, str>, Cow<'de, str>)>, +} + +impl<'de> de::SeqAccess<'de> for ParamsSeq<'de> +{ + type Error = de::value::Error; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where T: de::DeserializeSeed<'de>, + { + match self.params.next() { + Some(item) => Ok(Some(seed.deserialize(Value { value: item.1.as_ref() })?)), + None => Ok(None), + } + } +} + +struct ValueEnum<'de> { + value: &'de str, +} + +impl<'de> de::EnumAccess<'de> for ValueEnum<'de> { + type Error = de::value::Error; + type Variant = UnitVariant; + + fn variant_seed(self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where V: de::DeserializeSeed<'de>, + { + Ok((seed.deserialize(Key { key: self.value })?, UnitVariant)) + } +} + +struct UnitVariant; + +impl<'de> de::VariantAccess<'de> for UnitVariant { + type Error = de::value::Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, _seed: T) -> Result + where T: de::DeserializeSeed<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn tuple_variant(self, _len: usize, _visitor: V) -> Result + where V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } + + fn struct_variant(self, _: &'static [&'static str], _: V) + -> Result + where V: Visitor<'de>, + { + Err(de::value::Error::custom("not supported")) + } +} + #[cfg(test)] mod tests { use futures::{Async, Future}; @@ -306,6 +565,12 @@ mod tests { id: String, } + #[derive(Deserialize)] + struct Test2 { + key: String, + value: u32, + } + #[test] fn test_request_extract() { let mut req = TestRequest::with_uri("/name/user1/?id=test").finish(); @@ -339,5 +604,24 @@ mod tests { }, _ => unreachable!(), } + + let mut req = TestRequest::with_uri("/name/32/").finish(); + assert!(router.recognize(&mut req).is_some()); + + match Path::::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.key, "name"); + assert_eq!(s.value, 32); + }, + _ => unreachable!(), + } + + match Path::<(String, u8), _>::from_request(&req).poll().unwrap() { + Async::Ready(s) => { + assert_eq!(s.0, "name"); + assert_eq!(s.1, 32); + }, + _ => unreachable!(), + } } } diff --git a/src/lib.rs b/src/lib.rs index d02e52e2e..6a1b27c4e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ extern crate language_tags; extern crate rand; extern crate url; extern crate libc; -extern crate serde; +#[macro_use] extern crate serde; extern crate serde_json; extern crate serde_urlencoded; extern crate flate2;