From 4ca5d8bcfc7e5b32caeb8a585ec206f16f64329f Mon Sep 17 00:00:00 2001
From: Nikolay Kim <fafhrd91@gmail.com>
Date: Wed, 2 May 2018 13:38:25 -0700
Subject: [PATCH] add FromRequest impl for tuples of various length

---
 src/extractor.rs | 155 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 153 insertions(+), 2 deletions(-)

diff --git a/src/extractor.rs b/src/extractor.rs
index 12ae9e981..9d707e22f 100644
--- a/src/extractor.rs
+++ b/src/extractor.rs
@@ -1,17 +1,18 @@
+use std::marker::PhantomData;
 use std::ops::{Deref, DerefMut};
 use std::str;
 
 use bytes::Bytes;
 use encoding::all::UTF_8;
 use encoding::types::{DecoderTrap, Encoding};
-use futures::future::Future;
+use futures::{Async, Future, Poll};
 use mime::Mime;
 use serde::de::{self, DeserializeOwned};
 use serde_urlencoded;
 
 use de::PathDeserializer;
 use error::{Error, ErrorBadRequest};
-use handler::FromRequest;
+use handler::{FromRequest, Reply};
 use httpmessage::{HttpMessage, MessageBody, UrlEncoded};
 use httprequest::HttpRequest;
 
@@ -445,6 +446,123 @@ impl Default for PayloadConfig {
     }
 }
 
+macro_rules! tuple_from_req ({$fut_type:ident, $(($n:tt, $T:ident)),+} => {
+
+    /// FromRequest implementation for tuple
+    impl<S, $($T: FromRequest<S> + 'static),+> FromRequest<S> for ($($T,)+)
+    where
+        S: 'static,
+    {
+        type Config = ($($T::Config,)+);
+        type Result = Box<Future<Item = ($($T,)+), Error = Error>>;
+
+        fn from_request(req: &HttpRequest<S>, cfg: &Self::Config) -> Self::Result {
+            Box::new($fut_type {
+                s: PhantomData,
+                items: <($(Option<$T>,)+)>::default(),
+                futs: ($(Some($T::from_request(req, &cfg.$n).into()),)+),
+            })
+        }
+    }
+
+    struct $fut_type<S, $($T: FromRequest<S>),+>
+    where
+        S: 'static,
+    {
+        s: PhantomData<S>,
+        items: ($(Option<$T>,)+),
+        futs: ($(Option<Reply<$T>>,)+),
+    }
+
+    impl<S, $($T: FromRequest<S>),+> Future for $fut_type<S, $($T),+>
+    where
+        S: 'static,
+    {
+        type Item = ($($T,)+);
+        type Error = Error;
+
+        fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
+            let mut ready = true;
+
+            $(
+                if self.futs.$n.is_some() {
+                    match self.futs.$n.as_mut().unwrap().poll() {
+                        Ok(Async::Ready(item)) => {
+                            self.items.$n = Some(item);
+                            self.futs.$n.take();
+                        }
+                        Ok(Async::NotReady) => ready = false,
+                        Err(e) => return Err(e),
+                    }
+                }
+            )+
+
+                if ready {
+                    Ok(Async::Ready(
+                        ($(self.items.$n.take().unwrap(),)+)
+                    ))
+                } else {
+                    Ok(Async::NotReady)
+                }
+        }
+    }
+});
+
+tuple_from_req!(TupleFromRequest1, (0, A));
+tuple_from_req!(TupleFromRequest2, (0, A), (1, B));
+tuple_from_req!(TupleFromRequest3, (0, A), (1, B), (2, C));
+tuple_from_req!(TupleFromRequest4, (0, A), (1, B), (2, C), (3, D));
+tuple_from_req!(
+    TupleFromRequest5,
+    (0, A),
+    (1, B),
+    (2, C),
+    (3, D),
+    (4, E)
+);
+tuple_from_req!(
+    TupleFromRequest6,
+    (0, A),
+    (1, B),
+    (2, C),
+    (3, D),
+    (4, E),
+    (5, F)
+);
+tuple_from_req!(
+    TupleFromRequest7,
+    (0, A),
+    (1, B),
+    (2, C),
+    (3, D),
+    (4, E),
+    (5, F),
+    (6, G)
+);
+tuple_from_req!(
+    TupleFromRequest8,
+    (0, A),
+    (1, B),
+    (2, C),
+    (3, D),
+    (4, E),
+    (5, F),
+    (6, G),
+    (7, H)
+);
+tuple_from_req!(
+    TupleFromRequest9,
+    (0, A),
+    (1, B),
+    (2, C),
+    (3, D),
+    (4, E),
+    (5, F),
+    (6, G),
+    (7, H),
+    (8, I)
+);
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -609,4 +727,37 @@ mod tests {
 
         assert_eq!(*Path::<i8>::from_request(&mut req, &()).unwrap(), 32);
     }
+
+    #[test]
+    fn test_tuple_extract() {
+        let mut req = TestRequest::with_uri("/name/user1/?id=test").finish();
+
+        let mut resource = ResourceHandler::<()>::default();
+        resource.name("index");
+        let mut routes = Vec::new();
+        routes.push((
+            Resource::new("index", "/{key}/{value}/"),
+            Some(resource),
+        ));
+        let (router, _) = Router::new("", ServerSettings::default(), routes);
+        assert!(router.recognize(&mut req).is_some());
+
+        let res = match <(Path<(String, String)>,)>::extract(&req).poll() {
+            Ok(Async::Ready(res)) => res,
+            _ => panic!("error"),
+        };
+        assert_eq!((res.0).0, "name");
+        assert_eq!((res.0).1, "user1");
+
+        let res = match <(Path<(String, String)>, Path<(String, String)>)>::extract(&req)
+            .poll()
+        {
+            Ok(Async::Ready(res)) => res,
+            _ => panic!("error"),
+        };
+        assert_eq!((res.0).0, "name");
+        assert_eq!((res.0).1, "user1");
+        assert_eq!((res.1).0, "name");
+        assert_eq!((res.1).1, "user1");
+    }
 }