THRIFT-2945 Add Rust support
Client: Rust
Patch: Allen George <allen.george@gmail.com>

This closes #1147
diff --git a/tutorial/rs/src/bin/tutorial_client.rs b/tutorial/rs/src/bin/tutorial_client.rs
new file mode 100644
index 0000000..2b0d4f9
--- /dev/null
+++ b/tutorial/rs/src/bin/tutorial_client.rs
@@ -0,0 +1,136 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate clap;
+
+extern crate thrift;
+extern crate thrift_tutorial;
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+use thrift::protocol::{TInputProtocol, TOutputProtocol};
+use thrift::protocol::{TCompactInputProtocol, TCompactOutputProtocol};
+use thrift::transport::{TFramedTransport, TTcpTransport, TTransport};
+
+use thrift_tutorial::shared::TSharedServiceSyncClient;
+use thrift_tutorial::tutorial::{CalculatorSyncClient, TCalculatorSyncClient, Operation, Work};
+
+fn main() {
+    match run() {
+        Ok(()) => println!("tutorial client ran successfully"),
+        Err(e) => {
+            println!("tutorial client failed with error {:?}", e);
+            std::process::exit(1);
+        }
+    }
+}
+
+fn run() -> thrift::Result<()> {
+    let options = clap_app!(rust_tutorial_client =>
+        (version: "0.1.0")
+        (author: "Apache Thrift Developers <dev@thrift.apache.org>")
+        (about: "Thrift Rust tutorial client")
+        (@arg host: --host +takes_value "host on which the tutorial server listens")
+        (@arg port: --port +takes_value "port on which the tutorial server listens")
+    );
+    let matches = options.get_matches();
+
+    // get any passed-in args or the defaults
+    let host = matches.value_of("host").unwrap_or("127.0.0.1");
+    let port = value_t!(matches, "port", u16).unwrap_or(9090);
+
+    // build our client and connect to the host:port
+    let mut client = new_client(host, port)?;
+
+    // alright!
+    // let's start making some calls
+
+    // let's start with a ping; the server should respond
+    println!("ping!");
+    client.ping()?;
+
+    // simple add
+    println!("add");
+    let res = client.add(1, 2)?;
+    println!("added 1, 2 and got {}", res);
+
+    let logid = 32;
+
+    // let's do...a multiply!
+    let res = client.calculate(logid, Work::new(7, 8, Operation::MULTIPLY, None))?;
+    println!("multiplied 7 and 8 and got {}", res);
+
+    // let's get the log for it
+    let res = client.get_struct(32)?;
+    println!("got log {:?} for operation {}", res, logid);
+
+    // ok - let's be bad :(
+    // do a divide by 0
+    // logid doesn't matter; won't be recorded
+    let res = client.calculate(77, Work::new(2, 0, Operation::DIVIDE, "we bad".to_owned()));
+
+    // we should have gotten an exception back
+    match res {
+        Ok(v) => panic!("should not have succeeded with result {}", v),
+        Err(e) => println!("divide by zero failed with error {:?}", e),
+    }
+
+    // let's do a one-way call
+    println!("zip");
+    client.zip()?;
+
+    // and then close out with a final ping
+    println!("ping!");
+    client.ping()?;
+
+    Ok(())
+}
+
+fn new_client(host: &str, port: u16) -> thrift::Result<CalculatorSyncClient> {
+    let mut t = TTcpTransport::new();
+
+    // open the underlying TCP stream
+    println!("connecting to tutorial server on {}:{}", host, port);
+    let t = match t.open(&format!("{}:{}", host, port)) {
+        Ok(()) => t,
+        Err(e) => {
+            return Err(format!("failed to open tcp stream to {}:{} error:{:?}",
+                               host,
+                               port,
+                               e)
+                .into());
+        }
+    };
+
+    // refcounted because it's shared by both input and output transports
+    let t = Rc::new(RefCell::new(Box::new(t) as Box<TTransport>));
+
+    // wrap a raw socket (slow) with a buffered transport of some kind
+    let t = Box::new(TFramedTransport::new(t)) as Box<TTransport>;
+
+    // refcounted again because it's shared by both input and output protocols
+    let t = Rc::new(RefCell::new(t));
+
+    // now create the protocol implementations
+    let i_prot = Box::new(TCompactInputProtocol::new(t.clone())) as Box<TInputProtocol>;
+    let o_prot = Box::new(TCompactOutputProtocol::new(t.clone())) as Box<TOutputProtocol>;
+
+    // we're done!
+    Ok(CalculatorSyncClient::new(i_prot, o_prot))
+}
diff --git a/tutorial/rs/src/bin/tutorial_server.rs b/tutorial/rs/src/bin/tutorial_server.rs
new file mode 100644
index 0000000..9cc1866
--- /dev/null
+++ b/tutorial/rs/src/bin/tutorial_server.rs
@@ -0,0 +1,168 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#[macro_use]
+extern crate clap;
+
+extern crate thrift;
+extern crate thrift_tutorial;
+
+use std::collections::HashMap;
+use std::convert::{From, Into};
+use std::default::Default;
+
+use thrift::protocol::{TInputProtocolFactory, TOutputProtocolFactory};
+use thrift::protocol::{TCompactInputProtocolFactory, TCompactOutputProtocolFactory};
+use thrift::server::TSimpleServer;
+
+use thrift::transport::{TFramedTransportFactory, TTransportFactory};
+use thrift_tutorial::shared::{SharedServiceSyncHandler, SharedStruct};
+use thrift_tutorial::tutorial::{CalculatorSyncHandler, CalculatorSyncProcessor};
+use thrift_tutorial::tutorial::{InvalidOperation, Operation, Work};
+
+fn main() {
+    match run() {
+        Ok(()) => println!("tutorial server ran successfully"),
+        Err(e) => {
+            println!("tutorial server failed with error {:?}", e);
+            std::process::exit(1);
+        }
+    }
+}
+
+fn run() -> thrift::Result<()> {
+    let options = clap_app!(rust_tutorial_server =>
+        (version: "0.1.0")
+        (author: "Apache Thrift Developers <dev@thrift.apache.org>")
+        (about: "Thrift Rust tutorial server")
+        (@arg port: --port +takes_value "port on which the tutorial server listens")
+    );
+    let matches = options.get_matches();
+
+    let port = value_t!(matches, "port", u16).unwrap_or(9090);
+    let listen_address = format!("127.0.0.1:{}", port);
+
+    println!("binding to {}", listen_address);
+
+    let i_tran_fact: Box<TTransportFactory> = Box::new(TFramedTransportFactory::new());
+    let i_prot_fact: Box<TInputProtocolFactory> = Box::new(TCompactInputProtocolFactory::new());
+
+    let o_tran_fact: Box<TTransportFactory> = Box::new(TFramedTransportFactory::new());
+    let o_prot_fact: Box<TOutputProtocolFactory> = Box::new(TCompactOutputProtocolFactory::new());
+
+    // demux incoming messages
+    let processor = CalculatorSyncProcessor::new(CalculatorServer { ..Default::default() });
+
+    // create the server and start listening
+    let mut server = TSimpleServer::new(i_tran_fact,
+                                        i_prot_fact,
+                                        o_tran_fact,
+                                        o_prot_fact,
+                                        processor);
+
+    server.listen(&listen_address)
+}
+
+/// Handles incoming Calculator service calls.
+struct CalculatorServer {
+    log: HashMap<i32, SharedStruct>,
+}
+
+impl Default for CalculatorServer {
+    fn default() -> CalculatorServer {
+        CalculatorServer { log: HashMap::new() }
+    }
+}
+
+// since Calculator extends SharedService we have to implement the
+// handler for both traits.
+//
+
+// SharedService handler
+impl SharedServiceSyncHandler for CalculatorServer {
+    fn handle_get_struct(&mut self, key: i32) -> thrift::Result<SharedStruct> {
+        self.log
+            .get(&key)
+            .cloned()
+            .ok_or_else(|| format!("could not find log for key {}", key).into())
+    }
+}
+
+// Calculator handler
+impl CalculatorSyncHandler for CalculatorServer {
+    fn handle_ping(&mut self) -> thrift::Result<()> {
+        println!("pong!");
+        Ok(())
+    }
+
+    fn handle_add(&mut self, num1: i32, num2: i32) -> thrift::Result<i32> {
+        println!("handling add: n1:{} n2:{}", num1, num2);
+        Ok(num1 + num2)
+    }
+
+    fn handle_calculate(&mut self, logid: i32, w: Work) -> thrift::Result<i32> {
+        println!("handling calculate: l:{}, w:{:?}", logid, w);
+
+        let res = if let Some(ref op) = w.op {
+            if w.num1.is_none() || w.num2.is_none() {
+                Err(InvalidOperation {
+                    what_op: Some(*op as i32),
+                    why: Some("no operands specified".to_owned()),
+                })
+            } else {
+                // so that I don't have to call unwrap() multiple times below
+                let num1 = w.num1.as_ref().expect("operands checked");
+                let num2 = w.num2.as_ref().expect("operands checked");
+
+                match *op {
+                    Operation::ADD => Ok(num1 + num2),
+                    Operation::SUBTRACT => Ok(num1 - num2),
+                    Operation::MULTIPLY => Ok(num1 * num2),
+                    Operation::DIVIDE => {
+                        if *num2 == 0 {
+                            Err(InvalidOperation {
+                                what_op: Some(*op as i32),
+                                why: Some("divide by 0".to_owned()),
+                            })
+                        } else {
+                            Ok(num1 / num2)
+                        }
+                    }
+                }
+            }
+        } else {
+            Err(InvalidOperation::new(None, "no operation specified".to_owned()))
+        };
+
+        // if the operation was successful log it
+        if let Ok(ref v) = res {
+            self.log.insert(logid, SharedStruct::new(logid, format!("{}", v)));
+        }
+
+        // the try! macro automatically maps errors
+        // but, since we aren't using that here we have to map errors manually
+        //
+        // exception structs defined in the IDL have an auto-generated
+        // impl of From::from
+        res.map_err(From::from)
+    }
+
+    fn handle_zip(&mut self) -> thrift::Result<()> {
+        println!("handling zip");
+        Ok(())
+    }
+}
diff --git a/tutorial/rs/src/lib.rs b/tutorial/rs/src/lib.rs
new file mode 100644
index 0000000..40007e5
--- /dev/null
+++ b/tutorial/rs/src/lib.rs
@@ -0,0 +1,23 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+extern crate ordered_float;
+extern crate thrift;
+extern crate try_from;
+
+pub mod shared;
+pub mod tutorial;