THRIFT-4196 Support recursive types in Rust
Client: rs
Patch: Allen George <allen.george@gmail.com>

This closes #1267
diff --git a/lib/rs/test/Makefile.am b/lib/rs/test/Makefile.am
index 8896940..87208d7 100644
--- a/lib/rs/test/Makefile.am
+++ b/lib/rs/test/Makefile.am
@@ -19,11 +19,12 @@
 
 THRIFT = $(top_builddir)/compiler/cpp/thrift
 
-stubs: thrifts/Base_One.thrift thrifts/Base_Two.thrift thrifts/Midlayer.thrift thrifts/Ultimate.thrift $(THRIFT)
+stubs: thrifts/Base_One.thrift thrifts/Base_Two.thrift thrifts/Midlayer.thrift thrifts/Ultimate.thrift $(top_builddir)/test/Recursive.thrift $(THRIFT)
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Base_One.thrift
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Base_Two.thrift
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Midlayer.thrift
 	$(THRIFT) -I ./thrifts -out src --gen rs thrifts/Ultimate.thrift
+	$(THRIFT) -out src --gen rs $(top_builddir)/test/Recursive.thrift
 
 check: stubs
 	$(CARGO) build
diff --git a/lib/rs/test/src/bin/kitchen_sink_client.rs b/lib/rs/test/src/bin/kitchen_sink_client.rs
index 9738298..fb6ea15 100644
--- a/lib/rs/test/src/bin/kitchen_sink_client.rs
+++ b/lib/rs/test/src/bin/kitchen_sink_client.rs
@@ -21,8 +21,12 @@
 extern crate kitchen_sink;
 extern crate thrift;
 
+use std::convert::Into;
+
 use kitchen_sink::base_two::{TNapkinServiceSyncClient, TRamenServiceSyncClient};
 use kitchen_sink::midlayer::{MealServiceSyncClient, TMealServiceSyncClient};
+use kitchen_sink::recursive;
+use kitchen_sink::recursive::{CoRec, CoRec2, RecList, RecTree, TTestServiceSyncClient};
 use kitchen_sink::ultimate::{FullMealServiceSyncClient, TFullMealServiceSyncClient};
 use thrift::transport::{ReadHalf, TFramedReadTransport, TFramedWriteTransport, TIoChannel,
                         TTcpChannel, WriteHalf};
@@ -47,7 +51,7 @@
         (@arg host: --host +takes_value "Host on which the Thrift test server is located")
         (@arg port: --port +takes_value "Port on which the Thrift test server is listening")
         (@arg protocol: --protocol +takes_value "Thrift protocol implementation to use (\"binary\", \"compact\")")
-        (@arg service: --service +takes_value "Service type to contact (\"part\", \"full\")")
+        (@arg service: --service +takes_value "Service type to contact (\"part\", \"full\", \"recursive\")")
     )
             .get_matches();
 
@@ -80,8 +84,9 @@
     o_prot: Box<TOutputProtocol>,
 ) -> thrift::Result<()> {
     match service {
-        "full" => run_full_meal_service(i_prot, o_prot),
-        "part" => run_meal_service(i_prot, o_prot),
+        "full" => exec_full_meal_client(i_prot, o_prot),
+        "part" => exec_meal_client(i_prot, o_prot),
+        "recursive" => exec_recursive_client(i_prot, o_prot),
         _ => Err(thrift::Error::from(format!("unknown service type {}", service)),),
     }
 }
@@ -95,7 +100,7 @@
     c.split()
 }
 
-fn run_meal_service(
+fn exec_meal_client(
     i_prot: Box<TInputProtocol>,
     o_prot: Box<TOutputProtocol>,
 ) -> thrift::Result<()> {
@@ -105,28 +110,155 @@
     // this is because the MealService struct does not contain the appropriate service marker
 
     // only the following three calls work
-    execute_call("part", "ramen", || client.ramen(50))?;
-    execute_call("part", "meal", || client.meal())?;
-    execute_call("part", "napkin", || client.napkin())?;
+    execute_call("part", "ramen", || client.ramen(50))
+        .map(|_| ())?;
+    execute_call("part", "meal", || client.meal())
+        .map(|_| ())?;
+    execute_call("part", "napkin", || client.napkin())
+        .map(|_| ())?;
 
     Ok(())
 }
 
-fn run_full_meal_service(
+fn exec_full_meal_client(
     i_prot: Box<TInputProtocol>,
     o_prot: Box<TOutputProtocol>,
 ) -> thrift::Result<()> {
     let mut client = FullMealServiceSyncClient::new(i_prot, o_prot);
 
-    execute_call("full", "ramen", || client.ramen(100))?;
-    execute_call("full", "meal", || client.meal())?;
-    execute_call("full", "napkin", || client.napkin())?;
-    execute_call("full", "full meal", || client.full_meal())?;
+    execute_call("full", "ramen", || client.ramen(100))
+        .map(|_| ())?;
+    execute_call("full", "meal", || client.meal())
+        .map(|_| ())?;
+    execute_call("full", "napkin", || client.napkin())
+        .map(|_| ())?;
+    execute_call("full", "full meal", || client.full_meal())
+        .map(|_| ())?;
 
     Ok(())
 }
 
-fn execute_call<F, R>(service_type: &str, call_name: &str, mut f: F) -> thrift::Result<()>
+fn exec_recursive_client(
+    i_prot: Box<TInputProtocol>,
+    o_prot: Box<TOutputProtocol>,
+) -> thrift::Result<()> {
+    let mut client = recursive::TestServiceSyncClient::new(i_prot, o_prot);
+
+    let tree = RecTree {
+        children: Some(
+            vec![
+                Box::new(
+                    RecTree {
+                        children: Some(
+                            vec![
+                                Box::new(
+                                    RecTree {
+                                        children: None,
+                                        item: Some(3),
+                                    },
+                                ),
+                                Box::new(
+                                    RecTree {
+                                        children: None,
+                                        item: Some(4),
+                                    },
+                                ),
+                            ],
+                        ),
+                        item: Some(2),
+                    },
+                ),
+            ],
+        ),
+        item: Some(1),
+    };
+
+    let expected_tree = RecTree {
+        children: Some(
+            vec![
+                Box::new(
+                    RecTree {
+                        children: Some(
+                            vec![
+                                Box::new(
+                                    RecTree {
+                                        children: Some(Vec::new()), // remote returns an empty list
+                                        item: Some(3),
+                                    },
+                                ),
+                                Box::new(
+                                    RecTree {
+                                        children: Some(Vec::new()), // remote returns an empty list
+                                        item: Some(4),
+                                    },
+                                ),
+                            ],
+                        ),
+                        item: Some(2),
+                    },
+                ),
+            ],
+        ),
+        item: Some(1),
+    };
+
+    let returned_tree = execute_call("recursive", "echo_tree", || client.echo_tree(tree.clone()))?;
+    if returned_tree != expected_tree {
+        return Err(
+            format!(
+                "mismatched recursive tree {:?} {:?}",
+                expected_tree,
+                returned_tree
+            )
+                    .into(),
+        );
+    }
+
+    let list = RecList {
+        nextitem: Some(
+            Box::new(
+                RecList {
+                    nextitem: Some(
+                        Box::new(
+                            RecList {
+                                nextitem: None,
+                                item: Some(3),
+                            },
+                        ),
+                    ),
+                    item: Some(2),
+                },
+            ),
+        ),
+        item: Some(1),
+    };
+    let returned_list = execute_call("recursive", "echo_list", || client.echo_list(list.clone()))?;
+    if returned_list != list {
+        return Err(format!("mismatched recursive list {:?} {:?}", list, returned_list).into(),);
+    }
+
+    let co_rec = CoRec {
+        other: Some(
+            Box::new(
+                CoRec2 {
+                    other: Some(CoRec { other: Some(Box::new(CoRec2 { other: None })) }),
+                },
+            ),
+        ),
+    };
+    let returned_co_rec = execute_call(
+        "recursive",
+        "echo_co_rec",
+        || client.echo_co_rec(co_rec.clone()),
+    )?;
+    if returned_co_rec != co_rec {
+        return Err(format!("mismatched co_rec {:?} {:?}", co_rec, returned_co_rec).into(),);
+    }
+
+    Ok(())
+}
+
+fn execute_call<F, R>(service_type: &str, call_name: &str, mut f: F) -> thrift::Result<R>
 where
     F: FnMut() -> thrift::Result<R>,
 {
@@ -144,5 +276,5 @@
         }
     }
 
-    res.map(|_| ())
+    res
 }
diff --git a/lib/rs/test/src/bin/kitchen_sink_server.rs b/lib/rs/test/src/bin/kitchen_sink_server.rs
index 19112cd..15ceb29 100644
--- a/lib/rs/test/src/bin/kitchen_sink_server.rs
+++ b/lib/rs/test/src/bin/kitchen_sink_server.rs
@@ -24,6 +24,7 @@
 use kitchen_sink::base_one::Noodle;
 use kitchen_sink::base_two::{Napkin, NapkinServiceSyncHandler, Ramen, RamenServiceSyncHandler};
 use kitchen_sink::midlayer::{Dessert, Meal, MealServiceSyncHandler, MealServiceSyncProcessor};
+use kitchen_sink::recursive;
 use kitchen_sink::ultimate::{Drink, FullMeal, FullMealAndDrinks,
                              FullMealAndDrinksServiceSyncProcessor, FullMealServiceSyncHandler};
 use kitchen_sink::ultimate::FullMealAndDrinksServiceSyncHandler;
@@ -52,7 +53,7 @@
         (about: "Thrift Rust kitchen sink test server")
         (@arg port: --port +takes_value "port on which the test server listens")
         (@arg protocol: --protocol +takes_value "Thrift protocol implementation to use (\"binary\", \"compact\")")
-        (@arg service: --service +takes_value "Service type to contact (\"part\", \"full\")")
+        (@arg service: --service +takes_value "Service type to contact (\"part\", \"full\", \"recursive\")")
     )
             .get_matches();
 
@@ -111,6 +112,15 @@
                 o_protocol_factory,
             )
         }
+        "recursive" => {
+            run_recursive_server(
+                &listen_address,
+                r_transport_factory,
+                i_protocol_factory,
+                w_transport_factory,
+                o_protocol_factory,
+            )
+        }
         unknown => Err(format!("unsupported service type {}", unknown).into()),
     }
 }
@@ -248,3 +258,47 @@
 fn napkin() -> Napkin {
     Napkin {}
 }
+
+fn run_recursive_server<RTF, IPF, WTF, OPF>(
+    listen_address: &str,
+    r_transport_factory: RTF,
+    i_protocol_factory: IPF,
+    w_transport_factory: WTF,
+    o_protocol_factory: OPF,
+) -> thrift::Result<()>
+where
+    RTF: TReadTransportFactory + 'static,
+    IPF: TInputProtocolFactory + 'static,
+    WTF: TWriteTransportFactory + 'static,
+    OPF: TOutputProtocolFactory + 'static,
+{
+    let processor = recursive::TestServiceSyncProcessor::new(RecursiveTestServerHandler {});
+    let mut server = TServer::new(
+        r_transport_factory,
+        i_protocol_factory,
+        w_transport_factory,
+        o_protocol_factory,
+        processor,
+        1,
+    );
+
+    server.listen(listen_address)
+}
+
+struct RecursiveTestServerHandler;
+impl recursive::TestServiceSyncHandler for RecursiveTestServerHandler {
+    fn handle_echo_tree(&self, tree: recursive::RecTree) -> thrift::Result<recursive::RecTree> {
+        println!("{:?}", tree);
+        Ok(tree)
+    }
+
+    fn handle_echo_list(&self, lst: recursive::RecList) -> thrift::Result<recursive::RecList> {
+        println!("{:?}", lst);
+        Ok(lst)
+    }
+
+    fn handle_echo_co_rec(&self, item: recursive::CoRec) -> thrift::Result<recursive::CoRec> {
+        println!("{:?}", item);
+        Ok(item)
+    }
+}
diff --git a/lib/rs/test/src/lib.rs b/lib/rs/test/src/lib.rs
index 53f4873..e5e176e 100644
--- a/lib/rs/test/src/lib.rs
+++ b/lib/rs/test/src/lib.rs
@@ -23,6 +23,7 @@
 pub mod base_two;
 pub mod midlayer;
 pub mod ultimate;
+pub mod recursive;
 
 #[cfg(test)]
 mod tests {