tower_web/extract/
str.rs

1use codegen::CallSite;
2use extract::{Context, Error, Extract, ExtractFuture};
3use extract::bytes::ExtractBytes;
4use percent_encoding;
5use std::borrow::Cow;
6use util::BufStream;
7
8use futures::{Poll, Async};
9
10#[derive(Debug)]
11pub struct ExtractString<B> {
12    inner: Option<ExtractBytes<Vec<u8>, B>>,
13    decode: bool,
14    item: Option<String>,
15}
16
17impl<B: BufStream> Extract<B> for String {
18    type Future = ExtractString<B>;
19
20    fn extract(ctx: &Context) -> Self::Future {
21        use codegen::Source::*;
22
23        let inner = Vec::extract(ctx);
24
25        match ctx.callsite().source() {
26            Capture(_) | QueryString => {
27                ExtractString {
28                    inner: Some(inner),
29                    decode: true,
30                    item: None,
31                }
32            }
33            _ => {
34                ExtractString {
35                    inner: Some(inner),
36                    decode: false,
37                    item: None,
38               }
39            }
40        }
41    }
42
43    fn extract_body(ctx: &Context, body: B) -> Self::Future {
44        ExtractString {
45            inner: Some(Vec::extract_body(ctx, body)),
46            decode: false,
47            item: None,
48        }
49    }
50
51    fn requires_body(callsite: &CallSite) -> bool {
52        <Vec<u8> as Extract<B>>::requires_body(callsite)
53    }
54}
55
56impl<B> ExtractFuture for ExtractString<B>
57where
58    B: BufStream,
59{
60    type Item = String;
61
62    fn poll(&mut self) -> Poll<(), Error> {
63        try_ready!(self.inner.as_mut().unwrap().poll());
64
65        let bytes = self.inner.take().unwrap().extract();
66
67        let mut string = String::from_utf8(bytes)
68            .map_err(|_| {
69                Error::invalid_argument(&"invalid UTF-8 string")
70            })?;
71
72        if self.decode {
73            string = decode(&string)?;
74        }
75
76        self.item = Some(string);
77
78        Ok(Async::Ready(()))
79    }
80
81    fn extract(self) -> String {
82        self.item.unwrap()
83    }
84}
85
86fn decode(s: &str) -> Result<String, Error> {
87    percent_encoding::percent_decode(s.as_bytes())
88        .decode_utf8()
89        .map(Cow::into_owned)
90        .map_err(|e| Error::invalid_argument(&e))
91}
92
93#[cfg(test)]
94mod test {
95    use super::*;
96
97    #[test]
98    fn extract() {
99        assert_eq!("hello, world", decode("hello,%20world").unwrap());
100        assert!(decode("%ff").unwrap_err().is_invalid_argument());
101    }
102}