http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/configure.ac ---------------------------------------------------------------------- diff --git a/configure.ac b/configure.ac index dad10a7..0452a15 100755 --- a/configure.ac +++ b/configure.ac @@ -129,6 +129,7 @@ if test "$enable_libs" = "no"; then with_d="no" with_nodejs="no" with_lua="no" + with_rs="no" fi @@ -410,6 +411,29 @@ if test "$with_go" = "yes"; then fi AM_CONDITIONAL(WITH_GO, [test "$have_go" = "yes"]) +AX_THRIFT_LIB(rs, [Rust], yes) +have_rs="no" +if test "$with_rs" = "yes"; then + AC_PATH_PROG([CARGO], [cargo]) + AC_PATH_PROG([RUSTC], [rustc]) + if [[ -x "$CARGO" ]] && [[ -x "$RUSTC" ]]; then + min_rustc_version="1.13" + + AC_MSG_CHECKING([for rustc version]) + rustc_version=`$RUSTC --version 2>&1 | $SED -e 's/\(rustc \)\([0-9]\)\.\([0-9][0-9]*\)\.\([0-9][0-9]*\).*/\2.\3/'` + AC_MSG_RESULT($rustc_version) + AC_SUBST([rustc_version],[$rustc_version]) + + AX_COMPARE_VERSION([$min_rustc_version],[le],[$rustc_version],[ + : + have_rs="yes" + ],[ + : + have_rs="no" + ]) + fi +fi +AM_CONDITIONAL(WITH_RS, [test "$have_rs" = "yes"]) AX_THRIFT_LIB(haxe, [Haxe], yes) if test "$with_haxe" = "yes"; then @@ -777,6 +801,8 @@ AC_CONFIG_FILES([ lib/dart/Makefile lib/py/Makefile lib/rb/Makefile + lib/rs/Makefile + lib/rs/test/Makefile lib/lua/Makefile lib/xml/Makefile lib/xml/test/Makefile @@ -798,6 +824,7 @@ AC_CONFIG_FILES([ test/py.twisted/Makefile test/py.tornado/Makefile test/rb/Makefile + test/rs/Makefile tutorial/Makefile tutorial/c_glib/Makefile tutorial/cpp/Makefile @@ -814,6 +841,7 @@ AC_CONFIG_FILES([ tutorial/py.twisted/Makefile tutorial/py.tornado/Makefile tutorial/rb/Makefile + tutorial/rs/Makefile ]) if test "$have_cpp" = "yes" ; then MAYBE_CPP="cpp" ; else MAYBE_CPP="" ; fi @@ -848,6 +876,8 @@ if test "$have_erlang" = "yes" ; then MAYBE_ERLANG="erl" ; else MAYBE_ERLANG="" AC_SUBST([MAYBE_ERLANG]) if test "$have_lua" = "yes" ; then MAYBE_LUA="lua" ; else MAYBE_LUA="" ; fi AC_SUBST([MAYBE_LUA]) +if test "$have_rs" = "yes" ; then MAYBE_RS="rs" ; else MAYBE_RS="" ; fi +AC_SUBST([MAYBE_RS]) AC_OUTPUT @@ -873,6 +903,7 @@ echo "Building Go Library .......... : $have_go" echo "Building D Library ........... : $have_d" echo "Building NodeJS Library ...... : $have_nodejs" echo "Building Lua Library ......... : $have_lua" +echo "Building Rust Library ........ : $have_rs" if test "$have_cpp" = "yes" ; then echo @@ -974,6 +1005,13 @@ if test "$have_lua" = "yes" ; then echo "Lua Library:" echo " Using Lua .............. : $LUA" fi +if test "$have_rs" = "yes" ; then + echo + echo "Rust Library:" + echo " Using Cargo................ : $CARGO" + echo " Using rustc................ : $RUSTC" + echo " Using Rust version......... : $($RUSTC --version)" +fi echo echo "If something is missing that you think should be present," echo "please skim the output of configure to find the missing"
http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/Makefile.am ---------------------------------------------------------------------- diff --git a/lib/Makefile.am b/lib/Makefile.am index 21d807a..636f42c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -93,6 +93,10 @@ if WITH_LUA SUBDIRS += lua endif +if WITH_RS +SUBDIRS += rs +endif + # All of the libs that don't use Automake need to go in here # so they will end up in our release tarballs. EXTRA_DIST = \ http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/Cargo.toml ---------------------------------------------------------------------- diff --git a/lib/rs/Cargo.toml b/lib/rs/Cargo.toml new file mode 100644 index 0000000..07c5e67 --- /dev/null +++ b/lib/rs/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "thrift" +description = "Rust bindings for the Apache Thrift RPC system" +version = "1.0.0" +license = "Apache-2.0" +authors = ["Apache Thrift Developers <[email protected]>"] +homepage = "http://thrift.apache.org" +documentation = "https://thrift.apache.org" +readme = "README.md" +exclude = ["Makefile*", "test/**"] +keywords = ["thrift"] + +[dependencies] +integer-encoding = "1.0.3" +log = "~0.3.6" +byteorder = "0.5.3" +try_from = "0.2.0" + http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/Makefile.am ---------------------------------------------------------------------- diff --git a/lib/rs/Makefile.am b/lib/rs/Makefile.am new file mode 100644 index 0000000..0a34120 --- /dev/null +++ b/lib/rs/Makefile.am @@ -0,0 +1,46 @@ +# +# 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. +# + +SUBDIRS = . + +if WITH_TESTS +SUBDIRS += test +endif + +install: + @echo '##############################################################' + @echo '##############################################################' + @echo 'The Rust client library should be installed via a Cargo.toml dependency - please see /lib/rs/README.md' + @echo '##############################################################' + @echo '##############################################################' + +check-local: + $(CARGO) test + +all-local: + $(CARGO) build + +clean-local: + $(CARGO) clean + -$(RM) Cargo.lock + +EXTRA_DIST = \ + src \ + Cargo.toml \ + README.md http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/README.md ---------------------------------------------------------------------- diff --git a/lib/rs/README.md b/lib/rs/README.md new file mode 100644 index 0000000..8b35eda --- /dev/null +++ b/lib/rs/README.md @@ -0,0 +1,60 @@ +# Rust Thrift library + +## Overview + +This crate implements the components required to build a working Thrift server +and client. It is divided into the following modules: + + 1. errors + 2. protocol + 3. transport + 4. server + 5. autogen + +The modules are layered as shown. The `generated` layer is code generated by the +Thrift compiler's Rust plugin. It uses the components defined in this crate to +serialize and deserialize types and implement RPC. Users interact with these +types and services by writing their own code on top. + + ```text + +-----------+ + | app dev | + +-----------+ + | generated | <-> errors/results + +-----------+ + | protocol | + +-----------+ + | transport | + +-----------+ + ``` + +## Using this crate + +Add `thrift = "x.y.z"` to your `Cargo.toml`, where `x.y.z` is the version of the +Thrift compiler you're using. + +## API Documentation + +Full [Rustdoc](https://docs.rs/thrift/) + +## Contributing + +Bug reports and PRs are always welcome! Please see the +[Thrift website](https://thrift.apache.org/) for more details. + +Thrift Rust support requires code in several directories: + +* `compiler/cpp/src/thrift/generate/t_rs_generator.cc`: binding code generator +* `lib/rs`: runtime library +* `lib/rs/test`: supplemental tests +* `tutorial/rs`: tutorial client and server +* `test/rs`: cross-language test client and server + +All library code, test code and auto-generated code compiles and passes clippy +without warnings. All new code must do the same! When making changes ensure that: + +* `rustc` does does output any warnings +* `clippy` with default settings does not output any warnings (includes auto-generated code) +* `cargo test` is successful +* `make precross` and `make check` are successful +* `tutorial/bin/tutorial_client` and `tutorial/bin/tutorial_server` communicate http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/src/autogen.rs ---------------------------------------------------------------------- diff --git a/lib/rs/src/autogen.rs b/lib/rs/src/autogen.rs new file mode 100644 index 0000000..289c7be --- /dev/null +++ b/lib/rs/src/autogen.rs @@ -0,0 +1,45 @@ +// 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. + +//! Thrift compiler auto-generated support. +//! +//! +//! Types and functions used internally by the Thrift compiler's Rust plugin +//! to implement required functionality. Users should never have to use code +//! in this module directly. + +use ::protocol::{TInputProtocol, TOutputProtocol}; + +/// Specifies the minimum functionality an auto-generated client should provide +/// to communicate with a Thrift server. +pub trait TThriftClient { + /// Returns the input protocol used to read serialized Thrift messages + /// from the Thrift server. + fn i_prot_mut(&mut self) -> &mut TInputProtocol; + /// Returns the output protocol used to write serialized Thrift messages + /// to the Thrift server. + fn o_prot_mut(&mut self) -> &mut TOutputProtocol; + /// Returns the sequence number of the last message written to the Thrift + /// server. Returns `0` if no messages have been written. Sequence + /// numbers should *never* be negative, and this method returns an `i32` + /// simply because the Thrift protocol encodes sequence numbers as `i32` on + /// the wire. + fn sequence_number(&self) -> i32; // FIXME: consider returning a u32 + /// Increments the sequence number, indicating that a message with that + /// number has been sent to the Thrift server. + fn increment_sequence_number(&mut self) -> i32; +} http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/src/errors.rs ---------------------------------------------------------------------- diff --git a/lib/rs/src/errors.rs b/lib/rs/src/errors.rs new file mode 100644 index 0000000..a6049d5 --- /dev/null +++ b/lib/rs/src/errors.rs @@ -0,0 +1,678 @@ +// 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. + +use std::convert::{From, Into}; +use std::error::Error as StdError; +use std::fmt::{Debug, Display, Formatter}; +use std::{error, fmt, io, string}; +use try_from::TryFrom; + +use ::protocol::{TFieldIdentifier, TInputProtocol, TOutputProtocol, TStructIdentifier, TType}; + +// FIXME: should all my error structs impl error::Error as well? +// FIXME: should all fields in TransportError, ProtocolError and ApplicationError be optional? + +/// Error type returned by all runtime library functions. +/// +/// `thrift::Error` is used throughout this crate as well as in auto-generated +/// Rust code. It consists of four variants defined by convention across Thrift +/// implementations: +/// +/// 1. `Transport`: errors encountered while operating on I/O channels +/// 2. `Protocol`: errors encountered during runtime-library processing +/// 3. `Application`: errors encountered within auto-generated code +/// 4. `User`: IDL-defined exception structs +/// +/// The `Application` variant also functions as a catch-all: all handler errors +/// are automatically turned into application errors. +/// +/// All error variants except `Error::User` take an eponymous struct with two +/// required fields: +/// +/// 1. `kind`: variant-specific enum identifying the error sub-type +/// 2. `message`: human-readable error info string +/// +/// `kind` is defined by convention while `message` is freeform. If none of the +/// enumerated kinds are suitable use `Unknown`. +/// +/// To simplify error creation convenience constructors are defined for all +/// variants, and conversions from their structs (`thrift::TransportError`, +/// `thrift::ProtocolError` and `thrift::ApplicationError` into `thrift::Error`. +/// +/// # Examples +/// +/// Create a `TransportError`. +/// +/// ``` +/// use thrift; +/// use thrift::{TransportError, TransportErrorKind}; +/// +/// // explicit +/// let err0: thrift::Result<()> = Err( +/// thrift::Error::Transport( +/// TransportError { +/// kind: TransportErrorKind::TimedOut, +/// message: format!("connection to server timed out") +/// } +/// ) +/// ); +/// +/// // use conversion +/// let err1: thrift::Result<()> = Err( +/// thrift::Error::from( +/// TransportError { +/// kind: TransportErrorKind::TimedOut, +/// message: format!("connection to server timed out") +/// } +/// ) +/// ); +/// +/// // use struct constructor +/// let err2: thrift::Result<()> = Err( +/// thrift::Error::Transport( +/// TransportError::new( +/// TransportErrorKind::TimedOut, +/// "connection to server timed out" +/// ) +/// ) +/// ); +/// +/// +/// // use error variant constructor +/// let err3: thrift::Result<()> = Err( +/// thrift::new_transport_error( +/// TransportErrorKind::TimedOut, +/// "connection to server timed out" +/// ) +/// ); +/// ``` +/// +/// Create an error from a string. +/// +/// ``` +/// use thrift; +/// use thrift::{ApplicationError, ApplicationErrorKind}; +/// +/// // we just use `From::from` to convert a `String` into a `thrift::Error` +/// let err0: thrift::Result<()> = Err( +/// thrift::Error::from("This is an error") +/// ); +/// +/// // err0 is equivalent to... +/// let err1: thrift::Result<()> = Err( +/// thrift::Error::Application( +/// ApplicationError { +/// kind: ApplicationErrorKind::Unknown, +/// message: format!("This is an error") +/// } +/// ) +/// ); +/// ``` +/// +/// Return an IDL-defined exception. +/// +/// ```text +/// // Thrift IDL exception definition. +/// exception Xception { +/// 1: i32 errorCode, +/// 2: string message +/// } +/// ``` +/// +/// ``` +/// use std::convert::From; +/// use std::error::Error; +/// use std::fmt; +/// use std::fmt::{Display, Formatter}; +/// +/// // auto-generated by the Thrift compiler +/// #[derive(Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +/// pub struct Xception { +/// pub error_code: Option<i32>, +/// pub message: Option<String>, +/// } +/// +/// // auto-generated by the Thrift compiler +/// impl Error for Xception { +/// fn description(&self) -> &str { +/// "remote service threw Xception" +/// } +/// } +/// +/// // auto-generated by the Thrift compiler +/// impl From<Xception> for thrift::Error { +/// fn from(e: Xception) -> Self { +/// thrift::Error::User(Box::new(e)) +/// } +/// } +/// +/// // auto-generated by the Thrift compiler +/// impl Display for Xception { +/// fn fmt(&self, f: &mut Formatter) -> fmt::Result { +/// self.description().fmt(f) +/// } +/// } +/// +/// // in user code... +/// let err: thrift::Result<()> = Err( +/// thrift::Error::from(Xception { error_code: Some(1), message: None }) +/// ); +/// ``` +pub enum Error { + /// Errors encountered while operating on I/O channels. + /// + /// These include *connection closed* and *bind failure*. + Transport(TransportError), + /// Errors encountered during runtime-library processing. + /// + /// These include *message too large* and *unsupported protocol version*. + Protocol(ProtocolError), + /// Errors encountered within auto-generated code, or when incoming + /// or outgoing messages violate the Thrift spec. + /// + /// These include *out-of-order messages* and *missing required struct + /// fields*. + /// + /// This variant also functions as a catch-all: errors from handler + /// functions are automatically returned as an `ApplicationError`. + Application(ApplicationError), + /// IDL-defined exception structs. + User(Box<error::Error + Sync + Send>), +} + +impl Error { + /// Create an `ApplicationError` from its wire representation. + /// + /// Application code **should never** call this method directly. + pub fn read_application_error_from_in_protocol(i: &mut TInputProtocol) + -> ::Result<ApplicationError> { + let mut message = "general remote error".to_owned(); + let mut kind = ApplicationErrorKind::Unknown; + + i.read_struct_begin()?; + + loop { + let field_ident = i.read_field_begin()?; + + if field_ident.field_type == TType::Stop { + break; + } + + let id = field_ident.id.expect("sender should always specify id for non-STOP field"); + + match id { + 1 => { + let remote_message = i.read_string()?; + i.read_field_end()?; + message = remote_message; + } + 2 => { + let remote_type_as_int = i.read_i32()?; + let remote_kind: ApplicationErrorKind = TryFrom::try_from(remote_type_as_int) + .unwrap_or(ApplicationErrorKind::Unknown); + i.read_field_end()?; + kind = remote_kind; + } + _ => { + i.skip(field_ident.field_type)?; + } + } + } + + i.read_struct_end()?; + + Ok(ApplicationError { + kind: kind, + message: message, + }) + } + + /// Convert an `ApplicationError` into its wire representation and write + /// it to the remote. + /// + /// Application code **should never** call this method directly. + pub fn write_application_error_to_out_protocol(e: &ApplicationError, + o: &mut TOutputProtocol) + -> ::Result<()> { + o.write_struct_begin(&TStructIdentifier { name: "TApplicationException".to_owned() })?; + + let message_field = TFieldIdentifier::new("message", TType::String, 1); + let type_field = TFieldIdentifier::new("type", TType::I32, 2); + + o.write_field_begin(&message_field)?; + o.write_string(&e.message)?; + o.write_field_end()?; + + o.write_field_begin(&type_field)?; + o.write_i32(e.kind as i32)?; + o.write_field_end()?; + + o.write_field_stop()?; + o.write_struct_end()?; + + o.flush() + } +} + +impl error::Error for Error { + fn description(&self) -> &str { + match *self { + Error::Transport(ref e) => TransportError::description(e), + Error::Protocol(ref e) => ProtocolError::description(e), + Error::Application(ref e) => ApplicationError::description(e), + Error::User(ref e) => e.description(), + } + } +} + +impl Debug for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + Error::Transport(ref e) => Debug::fmt(e, f), + Error::Protocol(ref e) => Debug::fmt(e, f), + Error::Application(ref e) => Debug::fmt(e, f), + Error::User(ref e) => Debug::fmt(e, f), + } + } +} + +impl Display for Error { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match *self { + Error::Transport(ref e) => Display::fmt(e, f), + Error::Protocol(ref e) => Display::fmt(e, f), + Error::Application(ref e) => Display::fmt(e, f), + Error::User(ref e) => Display::fmt(e, f), + } + } +} + +impl From<String> for Error { + fn from(s: String) -> Self { + Error::Application(ApplicationError { + kind: ApplicationErrorKind::Unknown, + message: s, + }) + } +} + +impl<'a> From<&'a str> for Error { + fn from(s: &'a str) -> Self { + Error::Application(ApplicationError { + kind: ApplicationErrorKind::Unknown, + message: String::from(s), + }) + } +} + +impl From<TransportError> for Error { + fn from(e: TransportError) -> Self { + Error::Transport(e) + } +} + +impl From<ProtocolError> for Error { + fn from(e: ProtocolError) -> Self { + Error::Protocol(e) + } +} + +impl From<ApplicationError> for Error { + fn from(e: ApplicationError) -> Self { + Error::Application(e) + } +} + +/// Create a new `Error` instance of type `Transport` that wraps a +/// `TransportError`. +pub fn new_transport_error<S: Into<String>>(kind: TransportErrorKind, message: S) -> Error { + Error::Transport(TransportError::new(kind, message)) +} + +/// Information about I/O errors. +#[derive(Debug)] +pub struct TransportError { + /// I/O error variant. + /// + /// If a specific `TransportErrorKind` does not apply use + /// `TransportErrorKind::Unknown`. + pub kind: TransportErrorKind, + /// Human-readable error message. + pub message: String, +} + +impl TransportError { + /// Create a new `TransportError`. + pub fn new<S: Into<String>>(kind: TransportErrorKind, message: S) -> TransportError { + TransportError { + kind: kind, + message: message.into(), + } + } +} + +/// I/O error categories. +/// +/// This list may grow, and it is not recommended to match against it. +#[derive(Clone, Copy, Eq, Debug, PartialEq)] +pub enum TransportErrorKind { + /// Catch-all I/O error. + Unknown = 0, + /// An I/O operation was attempted when the transport channel was not open. + NotOpen = 1, + /// The transport channel cannot be opened because it was opened previously. + AlreadyOpen = 2, + /// An I/O operation timed out. + TimedOut = 3, + /// A read could not complete because no bytes were available. + EndOfFile = 4, + /// An invalid (buffer/message) size was requested or received. + NegativeSize = 5, + /// Too large a buffer or message size was requested or received. + SizeLimit = 6, +} + +impl TransportError { + fn description(&self) -> &str { + match self.kind { + TransportErrorKind::Unknown => "transport error", + TransportErrorKind::NotOpen => "not open", + TransportErrorKind::AlreadyOpen => "already open", + TransportErrorKind::TimedOut => "timed out", + TransportErrorKind::EndOfFile => "end of file", + TransportErrorKind::NegativeSize => "negative size message", + TransportErrorKind::SizeLimit => "message too long", + } + } +} + +impl Display for TransportError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl TryFrom<i32> for TransportErrorKind { + type Err = Error; + fn try_from(from: i32) -> Result<Self, Self::Err> { + match from { + 0 => Ok(TransportErrorKind::Unknown), + 1 => Ok(TransportErrorKind::NotOpen), + 2 => Ok(TransportErrorKind::AlreadyOpen), + 3 => Ok(TransportErrorKind::TimedOut), + 4 => Ok(TransportErrorKind::EndOfFile), + 5 => Ok(TransportErrorKind::NegativeSize), + 6 => Ok(TransportErrorKind::SizeLimit), + _ => { + Err(Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::Unknown, + message: format!("cannot convert {} to TransportErrorKind", from), + })) + } + } + } +} + +impl From<io::Error> for Error { + fn from(err: io::Error) -> Self { + match err.kind() { + io::ErrorKind::ConnectionReset | + io::ErrorKind::ConnectionRefused | + io::ErrorKind::NotConnected => { + Error::Transport(TransportError { + kind: TransportErrorKind::NotOpen, + message: err.description().to_owned(), + }) + } + io::ErrorKind::AlreadyExists => { + Error::Transport(TransportError { + kind: TransportErrorKind::AlreadyOpen, + message: err.description().to_owned(), + }) + } + io::ErrorKind::TimedOut => { + Error::Transport(TransportError { + kind: TransportErrorKind::TimedOut, + message: err.description().to_owned(), + }) + } + io::ErrorKind::UnexpectedEof => { + Error::Transport(TransportError { + kind: TransportErrorKind::EndOfFile, + message: err.description().to_owned(), + }) + } + _ => { + Error::Transport(TransportError { + kind: TransportErrorKind::Unknown, + message: err.description().to_owned(), // FIXME: use io error's debug string + }) + } + } + } +} + +impl From<string::FromUtf8Error> for Error { + fn from(err: string::FromUtf8Error) -> Self { + Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::InvalidData, + message: err.description().to_owned(), // FIXME: use fmt::Error's debug string + }) + } +} + +/// Create a new `Error` instance of type `Protocol` that wraps a +/// `ProtocolError`. +pub fn new_protocol_error<S: Into<String>>(kind: ProtocolErrorKind, message: S) -> Error { + Error::Protocol(ProtocolError::new(kind, message)) +} + +/// Information about errors that occur in the runtime library. +#[derive(Debug)] +pub struct ProtocolError { + /// Protocol error variant. + /// + /// If a specific `ProtocolErrorKind` does not apply use + /// `ProtocolErrorKind::Unknown`. + pub kind: ProtocolErrorKind, + /// Human-readable error message. + pub message: String, +} + +impl ProtocolError { + /// Create a new `ProtocolError`. + pub fn new<S: Into<String>>(kind: ProtocolErrorKind, message: S) -> ProtocolError { + ProtocolError { + kind: kind, + message: message.into(), + } + } +} + +/// Runtime library error categories. +/// +/// This list may grow, and it is not recommended to match against it. +#[derive(Clone, Copy, Eq, Debug, PartialEq)] +pub enum ProtocolErrorKind { + /// Catch-all runtime-library error. + Unknown = 0, + /// An invalid argument was supplied to a library function, or invalid data + /// was received from a Thrift endpoint. + InvalidData = 1, + /// An invalid size was received in an encoded field. + NegativeSize = 2, + /// Thrift message or field was too long. + SizeLimit = 3, + /// Unsupported or unknown Thrift protocol version. + BadVersion = 4, + /// Unsupported Thrift protocol, server or field type. + NotImplemented = 5, + /// Reached the maximum nested depth to which an encoded Thrift field could + /// be skipped. + DepthLimit = 6, +} + +impl ProtocolError { + fn description(&self) -> &str { + match self.kind { + ProtocolErrorKind::Unknown => "protocol error", + ProtocolErrorKind::InvalidData => "bad data", + ProtocolErrorKind::NegativeSize => "negative message size", + ProtocolErrorKind::SizeLimit => "message too long", + ProtocolErrorKind::BadVersion => "invalid thrift version", + ProtocolErrorKind::NotImplemented => "not implemented", + ProtocolErrorKind::DepthLimit => "maximum skip depth reached", + } + } +} + +impl Display for ProtocolError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl TryFrom<i32> for ProtocolErrorKind { + type Err = Error; + fn try_from(from: i32) -> Result<Self, Self::Err> { + match from { + 0 => Ok(ProtocolErrorKind::Unknown), + 1 => Ok(ProtocolErrorKind::InvalidData), + 2 => Ok(ProtocolErrorKind::NegativeSize), + 3 => Ok(ProtocolErrorKind::SizeLimit), + 4 => Ok(ProtocolErrorKind::BadVersion), + 5 => Ok(ProtocolErrorKind::NotImplemented), + 6 => Ok(ProtocolErrorKind::DepthLimit), + _ => { + Err(Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::Unknown, + message: format!("cannot convert {} to ProtocolErrorKind", from), + })) + } + } + } +} + +/// Create a new `Error` instance of type `Application` that wraps an +/// `ApplicationError`. +pub fn new_application_error<S: Into<String>>(kind: ApplicationErrorKind, message: S) -> Error { + Error::Application(ApplicationError::new(kind, message)) +} + +/// Information about errors in auto-generated code or in user-implemented +/// service handlers. +#[derive(Debug)] +pub struct ApplicationError { + /// Application error variant. + /// + /// If a specific `ApplicationErrorKind` does not apply use + /// `ApplicationErrorKind::Unknown`. + pub kind: ApplicationErrorKind, + /// Human-readable error message. + pub message: String, +} + +impl ApplicationError { + /// Create a new `ApplicationError`. + pub fn new<S: Into<String>>(kind: ApplicationErrorKind, message: S) -> ApplicationError { + ApplicationError { + kind: kind, + message: message.into(), + } + } +} + +/// Auto-generated or user-implemented code error categories. +/// +/// This list may grow, and it is not recommended to match against it. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum ApplicationErrorKind { + /// Catch-all application error. + Unknown = 0, + /// Made service call to an unknown service method. + UnknownMethod = 1, + /// Received an unknown Thrift message type. That is, not one of the + /// `thrift::protocol::TMessageType` variants. + InvalidMessageType = 2, + /// Method name in a service reply does not match the name of the + /// receiving service method. + WrongMethodName = 3, + /// Received an out-of-order Thrift message. + BadSequenceId = 4, + /// Service reply is missing required fields. + MissingResult = 5, + /// Auto-generated code failed unexpectedly. + InternalError = 6, + /// Thrift protocol error. When possible use `Error::ProtocolError` with a + /// specific `ProtocolErrorKind` instead. + ProtocolError = 7, + /// *Unknown*. Included only for compatibility with existing Thrift implementations. + InvalidTransform = 8, // ?? + /// Thrift endpoint requested, or is using, an unsupported encoding. + InvalidProtocol = 9, // ?? + /// Thrift endpoint requested, or is using, an unsupported auto-generated client type. + UnsupportedClientType = 10, // ?? +} + +impl ApplicationError { + fn description(&self) -> &str { + match self.kind { + ApplicationErrorKind::Unknown => "service error", + ApplicationErrorKind::UnknownMethod => "unknown service method", + ApplicationErrorKind::InvalidMessageType => "wrong message type received", + ApplicationErrorKind::WrongMethodName => "unknown method reply received", + ApplicationErrorKind::BadSequenceId => "out of order sequence id", + ApplicationErrorKind::MissingResult => "missing method result", + ApplicationErrorKind::InternalError => "remote service threw exception", + ApplicationErrorKind::ProtocolError => "protocol error", + ApplicationErrorKind::InvalidTransform => "invalid transform", + ApplicationErrorKind::InvalidProtocol => "invalid protocol requested", + ApplicationErrorKind::UnsupportedClientType => "unsupported protocol client", + } + } +} + +impl Display for ApplicationError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + write!(f, "{}", self.description()) + } +} + +impl TryFrom<i32> for ApplicationErrorKind { + type Err = Error; + fn try_from(from: i32) -> Result<Self, Self::Err> { + match from { + 0 => Ok(ApplicationErrorKind::Unknown), + 1 => Ok(ApplicationErrorKind::UnknownMethod), + 2 => Ok(ApplicationErrorKind::InvalidMessageType), + 3 => Ok(ApplicationErrorKind::WrongMethodName), + 4 => Ok(ApplicationErrorKind::BadSequenceId), + 5 => Ok(ApplicationErrorKind::MissingResult), + 6 => Ok(ApplicationErrorKind::InternalError), + 7 => Ok(ApplicationErrorKind::ProtocolError), + 8 => Ok(ApplicationErrorKind::InvalidTransform), + 9 => Ok(ApplicationErrorKind::InvalidProtocol), + 10 => Ok(ApplicationErrorKind::UnsupportedClientType), + _ => { + Err(Error::Application(ApplicationError { + kind: ApplicationErrorKind::Unknown, + message: format!("cannot convert {} to ApplicationErrorKind", from), + })) + } + } + } +} http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/src/lib.rs ---------------------------------------------------------------------- diff --git a/lib/rs/src/lib.rs b/lib/rs/src/lib.rs new file mode 100644 index 0000000..ad18721 --- /dev/null +++ b/lib/rs/src/lib.rs @@ -0,0 +1,87 @@ +// 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. + +//! Rust runtime library for the Apache Thrift RPC system. +//! +//! This crate implements the components required to build a working +//! Thrift server and client. It is divided into the following modules: +//! +//! 1. errors +//! 2. protocol +//! 3. transport +//! 4. server +//! 5. autogen +//! +//! The modules are layered as shown in the diagram below. The `generated` +//! layer is generated by the Thrift compiler's Rust plugin. It uses the +//! types and functions defined in this crate to serialize and deserialize +//! messages and implement RPC. Users interact with these types and services +//! by writing their own code on top. +//! +//! ```text +//! +-----------+ +//! | user app | +//! +-----------+ +//! | autogen'd | (uses errors, autogen) +//! +-----------+ +//! | protocol | +//! +-----------+ +//! | transport | +//! +-----------+ +//! ``` + +#![crate_type = "lib"] +#![doc(test(attr(allow(unused_variables), deny(warnings))))] + +extern crate byteorder; +extern crate integer_encoding; +extern crate try_from; + +#[macro_use] +extern crate log; + +// NOTE: this macro has to be defined before any modules. See: +// https://danielkeep.github.io/quick-intro-to-macros.html#some-more-gotchas + +/// Assert that an expression returning a `Result` is a success. If it is, +/// return the value contained in the result, i.e. `expr.unwrap()`. +#[cfg(test)] +macro_rules! assert_success { + ($e: expr) => { + { + let res = $e; + assert!(res.is_ok()); + res.unwrap() + } + } +} + +pub mod protocol; +pub mod server; +pub mod transport; + +mod errors; +pub use errors::*; + +mod autogen; +pub use autogen::*; + +/// Result type returned by all runtime library functions. +/// +/// As is convention this is a typedef of `std::result::Result` +/// with `E` defined as the `thrift::Error` type. +pub type Result<T> = std::result::Result<T, self::Error>; http://git-wip-us.apache.org/repos/asf/thrift/blob/8b96bfbf/lib/rs/src/protocol/binary.rs ---------------------------------------------------------------------- diff --git a/lib/rs/src/protocol/binary.rs b/lib/rs/src/protocol/binary.rs new file mode 100644 index 0000000..f3c9ea2 --- /dev/null +++ b/lib/rs/src/protocol/binary.rs @@ -0,0 +1,817 @@ +// 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. + +use byteorder::{BigEndian, ByteOrder, ReadBytesExt, WriteBytesExt}; +use std::cell::RefCell; +use std::convert::From; +use std::io::{Read, Write}; +use std::rc::Rc; +use try_from::TryFrom; + +use ::{ProtocolError, ProtocolErrorKind}; +use ::transport::TTransport; +use super::{TFieldIdentifier, TInputProtocol, TInputProtocolFactory, TListIdentifier, + TMapIdentifier, TMessageIdentifier, TMessageType}; +use super::{TOutputProtocol, TOutputProtocolFactory, TSetIdentifier, TStructIdentifier, TType}; + +const BINARY_PROTOCOL_VERSION_1: u32 = 0x80010000; + +/// Read messages encoded in the Thrift simple binary encoding. +/// +/// There are two available modes: `strict` and `non-strict`, where the +/// `non-strict` version does not check for the protocol version in the +/// received message header. +/// +/// # Examples +/// +/// Create and use a `TBinaryInputProtocol`. +/// +/// ```no_run +/// use std::cell::RefCell; +/// use std::rc::Rc; +/// use thrift::protocol::{TBinaryInputProtocol, TInputProtocol}; +/// use thrift::transport::{TTcpTransport, TTransport}; +/// +/// let mut transport = TTcpTransport::new(); +/// transport.open("localhost:9090").unwrap(); +/// let transport = Rc::new(RefCell::new(Box::new(transport) as Box<TTransport>)); +/// +/// let mut i_prot = TBinaryInputProtocol::new(transport, true); +/// +/// let recvd_bool = i_prot.read_bool().unwrap(); +/// let recvd_string = i_prot.read_string().unwrap(); +/// ``` +pub struct TBinaryInputProtocol { + strict: bool, + transport: Rc<RefCell<Box<TTransport>>>, +} + +impl TBinaryInputProtocol { + /// Create a `TBinaryInputProtocol` that reads bytes from `transport`. + /// + /// Set `strict` to `true` if all incoming messages contain the protocol + /// version number in the protocol header. + pub fn new(transport: Rc<RefCell<Box<TTransport>>>, strict: bool) -> TBinaryInputProtocol { + TBinaryInputProtocol { + strict: strict, + transport: transport, + } + } +} + +impl TInputProtocol for TBinaryInputProtocol { + #[cfg_attr(feature = "cargo-clippy", allow(collapsible_if))] + fn read_message_begin(&mut self) -> ::Result<TMessageIdentifier> { + let mut first_bytes = vec![0; 4]; + self.transport.borrow_mut().read_exact(&mut first_bytes[..])?; + + // the thrift version header is intentionally negative + // so the first check we'll do is see if the sign bit is set + // and if so - assume it's the protocol-version header + if first_bytes[0] >= 8 { + // apparently we got a protocol-version header - check + // it, and if it matches, read the rest of the fields + if first_bytes[0..2] != [0x80, 0x01] { + Err(::Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::BadVersion, + message: format!("received bad version: {:?}", &first_bytes[0..2]), + })) + } else { + let message_type: TMessageType = TryFrom::try_from(first_bytes[3])?; + let name = self.read_string()?; + let sequence_number = self.read_i32()?; + Ok(TMessageIdentifier::new(name, message_type, sequence_number)) + } + } else { + // apparently we didn't get a protocol-version header, + // which happens if the sender is not using the strict protocol + if self.strict { + // we're in strict mode however, and that always + // requires the protocol-version header to be written first + Err(::Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::BadVersion, + message: format!("received bad version: {:?}", &first_bytes[0..2]), + })) + } else { + // in the non-strict version the first message field + // is the message name. strings (byte arrays) are length-prefixed, + // so we've just read the length in the first 4 bytes + let name_size = BigEndian::read_i32(&first_bytes) as usize; + let mut name_buf: Vec<u8> = Vec::with_capacity(name_size); + self.transport.borrow_mut().read_exact(&mut name_buf)?; + let name = String::from_utf8(name_buf)?; + + // read the rest of the fields + let message_type: TMessageType = self.read_byte().and_then(TryFrom::try_from)?; + let sequence_number = self.read_i32()?; + Ok(TMessageIdentifier::new(name, message_type, sequence_number)) + } + } + } + + fn read_message_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn read_struct_begin(&mut self) -> ::Result<Option<TStructIdentifier>> { + Ok(None) + } + + fn read_struct_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn read_field_begin(&mut self) -> ::Result<TFieldIdentifier> { + let field_type_byte = self.read_byte()?; + let field_type = field_type_from_u8(field_type_byte)?; + let id = match field_type { + TType::Stop => Ok(0), + _ => self.read_i16(), + }?; + Ok(TFieldIdentifier::new::<Option<String>, String, i16>(None, field_type, id)) + } + + fn read_field_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn read_bytes(&mut self) -> ::Result<Vec<u8>> { + let num_bytes = self.transport.borrow_mut().read_i32::<BigEndian>()? as usize; + let mut buf = vec![0u8; num_bytes]; + self.transport.borrow_mut().read_exact(&mut buf).map(|_| buf).map_err(From::from) + } + + fn read_bool(&mut self) -> ::Result<bool> { + let b = self.read_i8()?; + match b { + 0 => Ok(false), + _ => Ok(true), + } + } + + fn read_i8(&mut self) -> ::Result<i8> { + self.transport.borrow_mut().read_i8().map_err(From::from) + } + + fn read_i16(&mut self) -> ::Result<i16> { + self.transport.borrow_mut().read_i16::<BigEndian>().map_err(From::from) + } + + fn read_i32(&mut self) -> ::Result<i32> { + self.transport.borrow_mut().read_i32::<BigEndian>().map_err(From::from) + } + + fn read_i64(&mut self) -> ::Result<i64> { + self.transport.borrow_mut().read_i64::<BigEndian>().map_err(From::from) + } + + fn read_double(&mut self) -> ::Result<f64> { + self.transport.borrow_mut().read_f64::<BigEndian>().map_err(From::from) + } + + fn read_string(&mut self) -> ::Result<String> { + let bytes = self.read_bytes()?; + String::from_utf8(bytes).map_err(From::from) + } + + fn read_list_begin(&mut self) -> ::Result<TListIdentifier> { + let element_type: TType = self.read_byte().and_then(field_type_from_u8)?; + let size = self.read_i32()?; + Ok(TListIdentifier::new(element_type, size)) + } + + fn read_list_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn read_set_begin(&mut self) -> ::Result<TSetIdentifier> { + let element_type: TType = self.read_byte().and_then(field_type_from_u8)?; + let size = self.read_i32()?; + Ok(TSetIdentifier::new(element_type, size)) + } + + fn read_set_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn read_map_begin(&mut self) -> ::Result<TMapIdentifier> { + let key_type: TType = self.read_byte().and_then(field_type_from_u8)?; + let value_type: TType = self.read_byte().and_then(field_type_from_u8)?; + let size = self.read_i32()?; + Ok(TMapIdentifier::new(key_type, value_type, size)) + } + + fn read_map_end(&mut self) -> ::Result<()> { + Ok(()) + } + + // utility + // + + fn read_byte(&mut self) -> ::Result<u8> { + self.transport.borrow_mut().read_u8().map_err(From::from) + } +} + +/// Factory for creating instances of `TBinaryInputProtocol`. +#[derive(Default)] +pub struct TBinaryInputProtocolFactory; + +impl TBinaryInputProtocolFactory { + /// Create a `TBinaryInputProtocolFactory`. + pub fn new() -> TBinaryInputProtocolFactory { + TBinaryInputProtocolFactory {} + } +} + +impl TInputProtocolFactory for TBinaryInputProtocolFactory { + fn create(&mut self, transport: Rc<RefCell<Box<TTransport>>>) -> Box<TInputProtocol> { + Box::new(TBinaryInputProtocol::new(transport, true)) as Box<TInputProtocol> + } +} + +/// Write messages using the Thrift simple binary encoding. +/// +/// There are two available modes: `strict` and `non-strict`, where the +/// `strict` version writes the protocol version number in the outgoing message +/// header and the `non-strict` version does not. +/// +/// # Examples +/// +/// Create and use a `TBinaryOutputProtocol`. +/// +/// ```no_run +/// use std::cell::RefCell; +/// use std::rc::Rc; +/// use thrift::protocol::{TBinaryOutputProtocol, TOutputProtocol}; +/// use thrift::transport::{TTcpTransport, TTransport}; +/// +/// let mut transport = TTcpTransport::new(); +/// transport.open("localhost:9090").unwrap(); +/// let transport = Rc::new(RefCell::new(Box::new(transport) as Box<TTransport>)); +/// +/// let mut o_prot = TBinaryOutputProtocol::new(transport, true); +/// +/// o_prot.write_bool(true).unwrap(); +/// o_prot.write_string("test_string").unwrap(); +/// ``` +pub struct TBinaryOutputProtocol { + strict: bool, + transport: Rc<RefCell<Box<TTransport>>>, +} + +impl TBinaryOutputProtocol { + /// Create a `TBinaryOutputProtocol` that writes bytes to `transport`. + /// + /// Set `strict` to `true` if all outgoing messages should contain the + /// protocol version number in the protocol header. + pub fn new(transport: Rc<RefCell<Box<TTransport>>>, strict: bool) -> TBinaryOutputProtocol { + TBinaryOutputProtocol { + strict: strict, + transport: transport, + } + } + + fn write_transport(&mut self, buf: &[u8]) -> ::Result<()> { + self.transport.borrow_mut().write(buf).map(|_| ()).map_err(From::from) + } +} + +impl TOutputProtocol for TBinaryOutputProtocol { + fn write_message_begin(&mut self, identifier: &TMessageIdentifier) -> ::Result<()> { + if self.strict { + let message_type: u8 = identifier.message_type.into(); + let header = BINARY_PROTOCOL_VERSION_1 | (message_type as u32); + self.transport.borrow_mut().write_u32::<BigEndian>(header)?; + self.write_string(&identifier.name)?; + self.write_i32(identifier.sequence_number) + } else { + self.write_string(&identifier.name)?; + self.write_byte(identifier.message_type.into())?; + self.write_i32(identifier.sequence_number) + } + } + + fn write_message_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn write_struct_begin(&mut self, _: &TStructIdentifier) -> ::Result<()> { + Ok(()) + } + + fn write_struct_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn write_field_begin(&mut self, identifier: &TFieldIdentifier) -> ::Result<()> { + if identifier.id.is_none() && identifier.field_type != TType::Stop { + return Err(::Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::Unknown, + message: format!("cannot write identifier {:?} without sequence number", + &identifier), + })); + } + + self.write_byte(field_type_to_u8(identifier.field_type))?; + if let Some(id) = identifier.id { + self.write_i16(id) + } else { + Ok(()) + } + } + + fn write_field_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn write_field_stop(&mut self) -> ::Result<()> { + self.write_byte(field_type_to_u8(TType::Stop)) + } + + fn write_bytes(&mut self, b: &[u8]) -> ::Result<()> { + self.write_i32(b.len() as i32)?; + self.write_transport(b) + } + + fn write_bool(&mut self, b: bool) -> ::Result<()> { + if b { + self.write_i8(1) + } else { + self.write_i8(0) + } + } + + fn write_i8(&mut self, i: i8) -> ::Result<()> { + self.transport.borrow_mut().write_i8(i).map_err(From::from) + } + + fn write_i16(&mut self, i: i16) -> ::Result<()> { + self.transport.borrow_mut().write_i16::<BigEndian>(i).map_err(From::from) + } + + fn write_i32(&mut self, i: i32) -> ::Result<()> { + self.transport.borrow_mut().write_i32::<BigEndian>(i).map_err(From::from) + } + + fn write_i64(&mut self, i: i64) -> ::Result<()> { + self.transport.borrow_mut().write_i64::<BigEndian>(i).map_err(From::from) + } + + fn write_double(&mut self, d: f64) -> ::Result<()> { + self.transport.borrow_mut().write_f64::<BigEndian>(d).map_err(From::from) + } + + fn write_string(&mut self, s: &str) -> ::Result<()> { + self.write_bytes(s.as_bytes()) + } + + fn write_list_begin(&mut self, identifier: &TListIdentifier) -> ::Result<()> { + self.write_byte(field_type_to_u8(identifier.element_type))?; + self.write_i32(identifier.size) + } + + fn write_list_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn write_set_begin(&mut self, identifier: &TSetIdentifier) -> ::Result<()> { + self.write_byte(field_type_to_u8(identifier.element_type))?; + self.write_i32(identifier.size) + } + + fn write_set_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn write_map_begin(&mut self, identifier: &TMapIdentifier) -> ::Result<()> { + let key_type = identifier.key_type + .expect("map identifier to write should contain key type"); + self.write_byte(field_type_to_u8(key_type))?; + let val_type = identifier.value_type + .expect("map identifier to write should contain value type"); + self.write_byte(field_type_to_u8(val_type))?; + self.write_i32(identifier.size) + } + + fn write_map_end(&mut self) -> ::Result<()> { + Ok(()) + } + + fn flush(&mut self) -> ::Result<()> { + self.transport.borrow_mut().flush().map_err(From::from) + } + + // utility + // + + fn write_byte(&mut self, b: u8) -> ::Result<()> { + self.transport.borrow_mut().write_u8(b).map_err(From::from) + } +} + +/// Factory for creating instances of `TBinaryOutputProtocol`. +#[derive(Default)] +pub struct TBinaryOutputProtocolFactory; + +impl TBinaryOutputProtocolFactory { + /// Create a `TBinaryOutputProtocolFactory`. + pub fn new() -> TBinaryOutputProtocolFactory { + TBinaryOutputProtocolFactory {} + } +} + +impl TOutputProtocolFactory for TBinaryOutputProtocolFactory { + fn create(&mut self, transport: Rc<RefCell<Box<TTransport>>>) -> Box<TOutputProtocol> { + Box::new(TBinaryOutputProtocol::new(transport, true)) as Box<TOutputProtocol> + } +} + +fn field_type_to_u8(field_type: TType) -> u8 { + match field_type { + TType::Stop => 0x00, + TType::Void => 0x01, + TType::Bool => 0x02, + TType::I08 => 0x03, // equivalent to TType::Byte + TType::Double => 0x04, + TType::I16 => 0x06, + TType::I32 => 0x08, + TType::I64 => 0x0A, + TType::String | TType::Utf7 => 0x0B, + TType::Struct => 0x0C, + TType::Map => 0x0D, + TType::Set => 0x0E, + TType::List => 0x0F, + TType::Utf8 => 0x10, + TType::Utf16 => 0x11, + } +} + +fn field_type_from_u8(b: u8) -> ::Result<TType> { + match b { + 0x00 => Ok(TType::Stop), + 0x01 => Ok(TType::Void), + 0x02 => Ok(TType::Bool), + 0x03 => Ok(TType::I08), // Equivalent to TType::Byte + 0x04 => Ok(TType::Double), + 0x06 => Ok(TType::I16), + 0x08 => Ok(TType::I32), + 0x0A => Ok(TType::I64), + 0x0B => Ok(TType::String), // technically, also a UTF7, but we'll treat it as string + 0x0C => Ok(TType::Struct), + 0x0D => Ok(TType::Map), + 0x0E => Ok(TType::Set), + 0x0F => Ok(TType::List), + 0x10 => Ok(TType::Utf8), + 0x11 => Ok(TType::Utf16), + unkn => { + Err(::Error::Protocol(ProtocolError { + kind: ProtocolErrorKind::InvalidData, + message: format!("cannot convert {} to TType", unkn), + })) + } + } +} + +#[cfg(test)] +mod tests { + + use std::rc::Rc; + use std::cell::RefCell; + + use ::protocol::{TFieldIdentifier, TMessageIdentifier, TMessageType, TInputProtocol, + TListIdentifier, TMapIdentifier, TOutputProtocol, TSetIdentifier, + TStructIdentifier, TType}; + use ::transport::{TPassThruTransport, TTransport}; + use ::transport::mem::TBufferTransport; + + use super::*; + + #[test] + fn must_write_message_call_begin() { + let (trans, _, mut o_prot) = test_objects(); + + let ident = TMessageIdentifier::new("test", TMessageType::Call, 1); + assert!(o_prot.write_message_begin(&ident).is_ok()); + + let buf = trans.borrow().write_buffer_to_vec(); + + let expected: [u8; 16] = [0x80, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x74, 0x65, + 0x73, 0x74, 0x00, 0x00, 0x00, 0x01]; + + assert_eq!(&expected, buf.as_slice()); + } + + + #[test] + fn must_write_message_reply_begin() { + let (trans, _, mut o_prot) = test_objects(); + + let ident = TMessageIdentifier::new("test", TMessageType::Reply, 10); + assert!(o_prot.write_message_begin(&ident).is_ok()); + + let buf = trans.borrow().write_buffer_to_vec(); + + let expected: [u8; 16] = [0x80, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x74, 0x65, + 0x73, 0x74, 0x00, 0x00, 0x00, 0x0A]; + + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_strict_message_begin() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let sent_ident = TMessageIdentifier::new("test", TMessageType::Call, 1); + assert!(o_prot.write_message_begin(&sent_ident).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let received_ident = assert_success!(i_prot.read_message_begin()); + assert_eq!(&received_ident, &sent_ident); + } + + #[test] + fn must_write_message_end() { + assert_no_write(|o| o.write_message_end()); + } + + #[test] + fn must_write_struct_begin() { + assert_no_write(|o| o.write_struct_begin(&TStructIdentifier::new("foo"))); + } + + #[test] + fn must_write_struct_end() { + assert_no_write(|o| o.write_struct_end()); + } + + #[test] + fn must_write_field_begin() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_field_begin(&TFieldIdentifier::new("some_field", TType::String, 22)) + .is_ok()); + + let expected: [u8; 3] = [0x0B, 0x00, 0x16]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_field_begin() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let sent_field_ident = TFieldIdentifier::new("foo", TType::I64, 20); + assert!(o_prot.write_field_begin(&sent_field_ident).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let expected_ident = TFieldIdentifier { + name: None, + field_type: TType::I64, + id: Some(20), + }; // no name + let received_ident = assert_success!(i_prot.read_field_begin()); + assert_eq!(&received_ident, &expected_ident); + } + + #[test] + fn must_write_stop_field() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_field_stop().is_ok()); + + let expected: [u8; 1] = [0x00]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_field_stop() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + assert!(o_prot.write_field_stop().is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let expected_ident = TFieldIdentifier { + name: None, + field_type: TType::Stop, + id: Some(0), + }; // we get id 0 + + let received_ident = assert_success!(i_prot.read_field_begin()); + assert_eq!(&received_ident, &expected_ident); + } + + #[test] + fn must_write_field_end() { + assert_no_write(|o| o.write_field_end()); + } + + #[test] + fn must_write_list_begin() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_list_begin(&TListIdentifier::new(TType::Bool, 5)).is_ok()); + + let expected: [u8; 5] = [0x02, 0x00, 0x00, 0x00, 0x05]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_list_begin() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let ident = TListIdentifier::new(TType::List, 900); + assert!(o_prot.write_list_begin(&ident).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let received_ident = assert_success!(i_prot.read_list_begin()); + assert_eq!(&received_ident, &ident); + } + + #[test] + fn must_write_list_end() { + assert_no_write(|o| o.write_list_end()); + } + + #[test] + fn must_write_set_begin() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_set_begin(&TSetIdentifier::new(TType::I16, 7)).is_ok()); + + let expected: [u8; 5] = [0x06, 0x00, 0x00, 0x00, 0x07]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_set_begin() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let ident = TSetIdentifier::new(TType::I64, 2000); + assert!(o_prot.write_set_begin(&ident).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let received_ident_result = i_prot.read_set_begin(); + assert!(received_ident_result.is_ok()); + assert_eq!(&received_ident_result.unwrap(), &ident); + } + + #[test] + fn must_write_set_end() { + assert_no_write(|o| o.write_set_end()); + } + + #[test] + fn must_write_map_begin() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_map_begin(&TMapIdentifier::new(TType::I64, TType::Struct, 32)) + .is_ok()); + + let expected: [u8; 6] = [0x0A, 0x0C, 0x00, 0x00, 0x00, 0x20]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_round_trip_map_begin() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let ident = TMapIdentifier::new(TType::Map, TType::Set, 100); + assert!(o_prot.write_map_begin(&ident).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let received_ident = assert_success!(i_prot.read_map_begin()); + assert_eq!(&received_ident, &ident); + } + + #[test] + fn must_write_map_end() { + assert_no_write(|o| o.write_map_end()); + } + + #[test] + fn must_write_bool_true() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_bool(true).is_ok()); + + let expected: [u8; 1] = [0x01]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_write_bool_false() { + let (trans, _, mut o_prot) = test_objects(); + + assert!(o_prot.write_bool(false).is_ok()); + + let expected: [u8; 1] = [0x00]; + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&expected, buf.as_slice()); + } + + #[test] + fn must_read_bool_true() { + let (trans, mut i_prot, _) = test_objects(); + + trans.borrow_mut().set_readable_bytes(&[0x01]); + + let read_bool = assert_success!(i_prot.read_bool()); + assert_eq!(read_bool, true); + } + + #[test] + fn must_read_bool_false() { + let (trans, mut i_prot, _) = test_objects(); + + trans.borrow_mut().set_readable_bytes(&[0x00]); + + let read_bool = assert_success!(i_prot.read_bool()); + assert_eq!(read_bool, false); + } + + #[test] + fn must_allow_any_non_zero_value_to_be_interpreted_as_bool_true() { + let (trans, mut i_prot, _) = test_objects(); + + trans.borrow_mut().set_readable_bytes(&[0xAC]); + + let read_bool = assert_success!(i_prot.read_bool()); + assert_eq!(read_bool, true); + } + + #[test] + fn must_write_bytes() { + let (trans, _, mut o_prot) = test_objects(); + + let bytes: [u8; 10] = [0x0A, 0xCC, 0xD1, 0x84, 0x99, 0x12, 0xAB, 0xBB, 0x45, 0xDF]; + + assert!(o_prot.write_bytes(&bytes).is_ok()); + + let buf = trans.borrow().write_buffer_to_vec(); + assert_eq!(&buf[0..4], [0x00, 0x00, 0x00, 0x0A]); // length + assert_eq!(&buf[4..], bytes); // actual bytes + } + + #[test] + fn must_round_trip_bytes() { + let (trans, mut i_prot, mut o_prot) = test_objects(); + + let bytes: [u8; 25] = [0x20, 0xFD, 0x18, 0x84, 0x99, 0x12, 0xAB, 0xBB, 0x45, 0xDF, 0x34, + 0xDC, 0x98, 0xA4, 0x6D, 0xF3, 0x99, 0xB4, 0xB7, 0xD4, 0x9C, 0xA5, + 0xB3, 0xC9, 0x88]; + + assert!(o_prot.write_bytes(&bytes).is_ok()); + + trans.borrow_mut().copy_write_buffer_to_read_buffer(); + + let received_bytes = assert_success!(i_prot.read_bytes()); + assert_eq!(&received_bytes, &bytes); + } + + fn test_objects + () + -> (Rc<RefCell<Box<TBufferTransport>>>, TBinaryInputProtocol, TBinaryOutputProtocol) + { + let mem = Rc::new(RefCell::new(Box::new(TBufferTransport::with_capacity(40, 40)))); + + let inner: Box<TTransport> = Box::new(TPassThruTransport { inner: mem.clone() }); + let inner = Rc::new(RefCell::new(inner)); + + let i_prot = TBinaryInputProtocol::new(inner.clone(), true); + let o_prot = TBinaryOutputProtocol::new(inner.clone(), true); + + (mem, i_prot, o_prot) + } + + fn assert_no_write<F: FnMut(&mut TBinaryOutputProtocol) -> ::Result<()>>(mut write_fn: F) { + let (trans, _, mut o_prot) = test_objects(); + assert!(write_fn(&mut o_prot).is_ok()); + assert_eq!(trans.borrow().write_buffer_as_ref().len(), 0); + } +}
