summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--compiler/cpp/src/thrift/generate/t_rs_generator.cc133
-rw-r--r--lib/rs/README.md47
-rw-r--r--lib/rs/test/src/bin/kitchen_sink_server.rs6
-rw-r--r--test/rs/src/bin/test_client.rs14
-rw-r--r--test/rs/src/bin/test_server.rs6
-rw-r--r--tutorial/rs/src/bin/tutorial_client.rs4
-rw-r--r--tutorial/rs/src/bin/tutorial_server.rs21
7 files changed, 159 insertions, 72 deletions
diff --git a/compiler/cpp/src/thrift/generate/t_rs_generator.cc b/compiler/cpp/src/thrift/generate/t_rs_generator.cc
index f55b7e0db..5f9791a9b 100644
--- a/compiler/cpp/src/thrift/generate/t_rs_generator.cc
+++ b/compiler/cpp/src/thrift/generate/t_rs_generator.cc
@@ -116,7 +116,7 @@ private:
// Write the impl block associated with the rust representation of an enum. This includes methods
// to write the enum to a protocol, read it from a protocol, etc.
- void render_enum_impl(const string& enum_name);
+ void render_enum_impl(t_enum* tenum, const string& enum_name);
// Write a simple rust const value (ie. `pub const FOO: foo...`).
void render_const_value(const string& name, t_type* ttype, t_const_value* tvalue);
@@ -868,49 +868,62 @@ void t_rs_generator::generate_typedef(t_typedef* ttypedef) {
void t_rs_generator::generate_enum(t_enum* tenum) {
string enum_name(rust_camel_case(tenum->get_name()));
render_enum_definition(tenum, enum_name);
- render_enum_impl(enum_name);
+ render_enum_impl(tenum, enum_name);
render_enum_conversion(tenum, enum_name);
}
void t_rs_generator::render_enum_definition(t_enum* tenum, const string& enum_name) {
render_rustdoc((t_doc*) tenum);
f_gen_ << "#[derive(Copy, Clone, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]" << endl;
- f_gen_ << "pub enum " << enum_name << " {" << endl;
- indent_up();
-
- vector<t_enum_value*> constants = tenum->get_constants();
- vector<t_enum_value*>::iterator constants_iter;
- for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) {
- t_enum_value* val = (*constants_iter);
- render_rustdoc((t_doc*) val);
- f_gen_
- << indent()
- << rust_enum_variant_name(val->get_name())
- << " = "
- << val->get_value()
- << ","
- << endl;
- }
-
- indent_down();
- f_gen_ << "}" << endl;
+ f_gen_ << "pub struct " << enum_name << "(pub i32);" << endl;
f_gen_ << endl;
}
-void t_rs_generator::render_enum_impl(const string& enum_name) {
+void t_rs_generator::render_enum_impl(t_enum* tenum, const string& enum_name) {
f_gen_ << "impl " << enum_name << " {" << endl;
indent_up();
- // taking enum as 'self' here because Thrift enums
- // are represented as Rust enums with integer values
- // it's cheaper to copy the integer as opposed to
- // taking a reference to the enum
+ vector<t_enum_value*> constants = tenum->get_constants();
+
+ // associated constants for each IDL-defined enum variant
+ {
+ vector<t_enum_value*>::iterator constants_iter;
+ for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) {
+ t_enum_value* val = (*constants_iter);
+ render_rustdoc((t_doc*) val);
+ f_gen_
+ << indent()
+ << "pub const " << rust_enum_variant_name(val->get_name()) << ": " << enum_name
+ << " = "
+ << enum_name << "(" << val->get_value() << ")"
+ << ";"
+ << endl;
+ }
+ }
+
+ // array containing all IDL-defined enum variants
+ {
+ f_gen_ << indent() << "pub const ENUM_VALUES: &'static [Self] = &[" << endl;
+ indent_up();
+ vector<t_enum_value*>::iterator constants_iter;
+ for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) {
+ t_enum_value* val = (*constants_iter);
+ f_gen_
+ << indent()
+ << "Self::" << rust_enum_variant_name(val->get_name())
+ << ","
+ << endl;
+ }
+ indent_down();
+ f_gen_ << indent() << "];" << endl;
+ }
+
f_gen_
<< indent()
- << "pub fn write_to_out_protocol(self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {"
+ << "pub fn write_to_out_protocol(&self, o_prot: &mut dyn TOutputProtocol) -> thrift::Result<()> {"
<< endl;
indent_up();
- f_gen_ << indent() << "o_prot.write_i32(self as i32)" << endl;
+ f_gen_ << indent() << "o_prot.write_i32(self.0)" << endl;
indent_down();
f_gen_ << indent() << "}" << endl;
@@ -919,10 +932,8 @@ void t_rs_generator::render_enum_impl(const string& enum_name) {
<< "pub fn read_from_in_protocol(i_prot: &mut dyn TInputProtocol) -> thrift::Result<" << enum_name << "> {"
<< endl;
indent_up();
-
f_gen_ << indent() << "let enum_value = i_prot.read_i32()?;" << endl;
- f_gen_ << indent() << enum_name << "::try_from(enum_value)";
-
+ f_gen_ << indent() << "Ok(" << enum_name << "::from(enum_value)" << ")" << endl;
indent_down();
f_gen_ << indent() << "}" << endl;
@@ -932,17 +943,13 @@ void t_rs_generator::render_enum_impl(const string& enum_name) {
}
void t_rs_generator::render_enum_conversion(t_enum* tenum, const string& enum_name) {
- f_gen_ << "impl TryFrom<i32> for " << enum_name << " {" << endl;
+ // From trait: i32 -> ENUM_TYPE
+ f_gen_ << "impl From<i32> for " << enum_name << " {" << endl;
indent_up();
-
- f_gen_ << indent() << "type Error = thrift::Error;";
-
- f_gen_ << indent() << "fn try_from(i: i32) -> Result<Self, Self::Error> {" << endl;
+ f_gen_ << indent() << "fn from(i: i32) -> Self {" << endl;
indent_up();
-
f_gen_ << indent() << "match i {" << endl;
indent_up();
-
vector<t_enum_value*> constants = tenum->get_constants();
vector<t_enum_value*>::iterator constants_iter;
for (constants_iter = constants.begin(); constants_iter != constants.end(); ++constants_iter) {
@@ -950,26 +957,50 @@ void t_rs_generator::render_enum_conversion(t_enum* tenum, const string& enum_na
f_gen_
<< indent()
<< val->get_value()
- << " => Ok(" << enum_name << "::" << rust_enum_variant_name(val->get_name()) << "),"
+ << " => " << enum_name << "::" << rust_enum_variant_name(val->get_name()) << ","
<< endl;
}
- f_gen_ << indent() << "_ => {" << endl;
- indent_up();
- render_thrift_error(
- "Protocol",
- "ProtocolError",
- "ProtocolErrorKind::InvalidData",
- "format!(\"cannot convert enum constant {} to " + enum_name + "\", i)"
- );
+ f_gen_ << indent() << "_ => " << enum_name << "(i)" << endl;
indent_down();
- f_gen_ << indent() << "}," << endl;
+ f_gen_ << indent() << "}" << endl;
+ indent_down();
+ f_gen_ << indent() << "}" << endl;
+ indent_down();
+ f_gen_ << "}" << endl;
+ f_gen_ << endl;
+ // From trait: &i32 -> ENUM_TYPE
+ f_gen_ << "impl From<&i32> for " << enum_name << " {" << endl;
+ indent_up();
+ f_gen_ << indent() << "fn from(i: &i32) -> Self {" << endl;
+ indent_up();
+ f_gen_ << indent() << enum_name << "::from(*i)" << endl;
indent_down();
f_gen_ << indent() << "}" << endl;
+ indent_down();
+ f_gen_ << "}" << endl;
+ f_gen_ << endl;
+ // From trait: ENUM_TYPE -> int
+ f_gen_ << "impl From<" << enum_name << "> for i32 {" << endl;
+ indent_up();
+ f_gen_ << indent() << "fn from(e: " << enum_name << ") -> i32 {" << endl;
+ indent_up();
+ f_gen_ << indent() << "e.0" << endl;
indent_down();
f_gen_ << indent() << "}" << endl;
+ indent_down();
+ f_gen_ << "}" << endl;
+ f_gen_ << endl;
+ // From trait: &ENUM_TYPE -> int
+ f_gen_ << "impl From<&" << enum_name << "> for i32 {" << endl;
+ indent_up();
+ f_gen_ << indent() << "fn from(e: &" << enum_name << ") -> i32 {" << endl;
+ indent_up();
+ f_gen_ << indent() << "e.0" << endl;
+ indent_down();
+ f_gen_ << indent() << "}" << endl;
indent_down();
f_gen_ << "}" << endl;
f_gen_ << endl;
@@ -3295,9 +3326,11 @@ string t_rs_generator::rust_enum_variant_name(const string &name) {
}
if (all_uppercase) {
- return capitalize(camelcase(lowercase(name)));
+ return name;
} else {
- return capitalize(camelcase(name));
+ string modified_name(uppercase(underscore(name)));
+ string_replace(modified_name, "__", "_");
+ return modified_name;
}
}
diff --git a/lib/rs/README.md b/lib/rs/README.md
index 555d219d2..1e10e35e3 100644
--- a/lib/rs/README.md
+++ b/lib/rs/README.md
@@ -46,6 +46,51 @@ It does not currently use any Rust 2018 features.
Breaking changes are minimized. When they are made they will be outlined below with transition guidelines.
+##### Thrift 0.15.0
+
+* **[THRIFT-5314]** - Generate enums implementations that are forward compatible (i.e. don't error on unknown values)
+
+ As a result of this change the Rust representation of an enum changes from a standard
+ Rust enum into a newtype struct with associated constants.
+
+ For example:
+
+ ```thrift
+ // THRIFT
+ enum Operation {
+ ADD,
+ SUBTRACT,
+ MULTIPLY,
+ DIVIDE,
+ }
+ ```
+
+ used to generate:
+
+ ```rust
+ // OLD AUTO-GENERATED RUST
+ pub enum Operation {
+ Add,
+ Subtract,
+ Multiply,
+ Divide,
+ }
+ ```
+
+ It *now* generates:
+
+ ```rust
+ // NEW AUTO-GENERATED RUST
+ pub struct Operation(pub i32);
+
+ impl Operation {
+ pub const ADD: Operation = Operation(0);
+ pub const SUBTRACT: Operation = Operation(1);
+ pub const MULTIPLY: Operation = Operation(2);
+ pub const DIVIDE: Operation = Operation(3);
+ }
+ ```
+
##### Thrift 0.14.0
* **[THRIFT-5158]** - Rust library and generator now support Rust 2018 only. Required rust 1.40.0 or higher
@@ -91,7 +136,9 @@ Breaking changes are minimized. When they are made they will be outlined below w
DIVIDE,
}
```
+
It *now* generates:
+
```rust
// NEW AUTO-GENERATED RUST
pub enum Operation {
diff --git a/lib/rs/test/src/bin/kitchen_sink_server.rs b/lib/rs/test/src/bin/kitchen_sink_server.rs
index 6df39e4bc..8b910b3bf 100644
--- a/lib/rs/test/src/bin/kitchen_sink_server.rs
+++ b/lib/rs/test/src/bin/kitchen_sink_server.rs
@@ -209,12 +209,12 @@ struct FullHandler;
impl FullMealAndDrinksServiceSyncHandler for FullHandler {
fn handle_full_meal_and_drinks(&self) -> thrift::Result<FullMealAndDrinks> {
println!("full_meal_and_drinks: handling full meal and drinks call");
- Ok(FullMealAndDrinks::new(full_meal(), Drink::CanadianWhisky))
+ Ok(FullMealAndDrinks::new(full_meal(), Drink::CANADIAN_WHISKY))
}
fn handle_best_pie(&self) -> thrift::Result<Pie> {
println!("full_meal_and_drinks: handling pie call");
- Ok(Pie::MississippiMud) // I prefer Pie::Pumpkin, but I have to check that casing works
+ Ok(Pie::MISSISSIPPI_MUD) // I prefer Pie::Pumpkin, but I have to check that casing works
}
}
@@ -259,7 +259,7 @@ fn noodle() -> Noodle {
}
fn ramen() -> Ramen {
- Ramen::new("Mr Ramen".to_owned(), 72, BrothType::Miso)
+ Ramen::new("Mr Ramen".to_owned(), 72, BrothType::MISO)
}
fn napkin() -> Napkin {
diff --git a/test/rs/src/bin/test_client.rs b/test/rs/src/bin/test_client.rs
index 3e20999ed..476f9eb25 100644
--- a/test/rs/src/bin/test_client.rs
+++ b/test/rs/src/bin/test_client.rs
@@ -206,7 +206,7 @@ fn make_thrift_calls(
info!("testEnum");
{
- verify_expected_result(thrift_test_client.test_enum(Numberz::Two), Numberz::Two)?;
+ verify_expected_result(thrift_test_client.test_enum(Numberz::TWO), Numberz::TWO)?;
}
info!("testBinary");
@@ -381,7 +381,7 @@ fn make_thrift_calls(
};
verify_expected_result(
- thrift_test_client.test_multi(1, -123_948, -19_234_123_981, m_snd, Numberz::Eight, 81),
+ thrift_test_client.test_multi(1, -123_948, -19_234_123_981, m_snd, Numberz::EIGHT, 81),
s_cmp,
)?;
}
@@ -395,8 +395,8 @@ fn make_thrift_calls(
// }
{
let mut arg_map_usermap: BTreeMap<Numberz, i64> = BTreeMap::new();
- arg_map_usermap.insert(Numberz::One, 4289);
- arg_map_usermap.insert(Numberz::Eight, 19);
+ arg_map_usermap.insert(Numberz::ONE, 4289);
+ arg_map_usermap.insert(Numberz::EIGHT, 19);
let mut arg_vec_xtructs: Vec<Xtruct> = Vec::new();
arg_vec_xtructs.push(
@@ -429,15 +429,15 @@ fn make_thrift_calls(
user_map: Some(arg_map_usermap),
xtructs: Some(arg_vec_xtructs),
};
- s_cmp_nested_1.insert(Numberz::Two, insanity.clone());
- s_cmp_nested_1.insert(Numberz::Three, insanity.clone());
+ s_cmp_nested_1.insert(Numberz::TWO, insanity.clone());
+ s_cmp_nested_1.insert(Numberz::THREE, insanity.clone());
let mut s_cmp_nested_2: BTreeMap<Numberz, Insanity> = BTreeMap::new();
let empty_insanity = Insanity {
user_map: Some(BTreeMap::new()),
xtructs: Some(Vec::new()),
};
- s_cmp_nested_2.insert(Numberz::Six, empty_insanity);
+ s_cmp_nested_2.insert(Numberz::SIX, empty_insanity);
let mut s_cmp: BTreeMap<UserId, BTreeMap<Numberz, Insanity>> = BTreeMap::new();
s_cmp.insert(1 as UserId, s_cmp_nested_1);
diff --git a/test/rs/src/bin/test_server.rs b/test/rs/src/bin/test_server.rs
index 74be12d48..c1f31758a 100644
--- a/test/rs/src/bin/test_server.rs
+++ b/test/rs/src/bin/test_server.rs
@@ -268,15 +268,15 @@ impl ThriftTestSyncHandler for ThriftTestSyncHandlerImpl {
) -> thrift::Result<BTreeMap<UserId, BTreeMap<Numberz, Insanity>>> {
info!("testInsanity({:?})", argument);
let mut map_0: BTreeMap<Numberz, Insanity> = BTreeMap::new();
- map_0.insert(Numberz::Two, argument.clone());
- map_0.insert(Numberz::Three, argument);
+ map_0.insert(Numberz::TWO, argument.clone());
+ map_0.insert(Numberz::THREE, argument);
let mut map_1: BTreeMap<Numberz, Insanity> = BTreeMap::new();
let insanity = Insanity {
user_map: None,
xtructs: None,
};
- map_1.insert(Numberz::Six, insanity);
+ map_1.insert(Numberz::SIX, insanity);
let mut ret: BTreeMap<UserId, BTreeMap<Numberz, Insanity>> = BTreeMap::new();
ret.insert(1, map_0);
diff --git a/tutorial/rs/src/bin/tutorial_client.rs b/tutorial/rs/src/bin/tutorial_client.rs
index f7de23f06..4bf2ec098 100644
--- a/tutorial/rs/src/bin/tutorial_client.rs
+++ b/tutorial/rs/src/bin/tutorial_client.rs
@@ -67,7 +67,7 @@ fn run() -> thrift::Result<()> {
let logid = 32;
// let's do...a multiply!
- let res = client.calculate(logid, Work::new(7, 8, Operation::Multiply, None))?;
+ 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
@@ -77,7 +77,7 @@ fn run() -> thrift::Result<()> {
// 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()));
+ let res = client.calculate(77, Work::new(2, 0, Operation::DIVIDE, "we bad".to_owned()));
// we should have gotten an exception back
match res {
diff --git a/tutorial/rs/src/bin/tutorial_server.rs b/tutorial/rs/src/bin/tutorial_server.rs
index fbccb69c9..b6ed7a123 100644
--- a/tutorial/rs/src/bin/tutorial_server.rs
+++ b/tutorial/rs/src/bin/tutorial_server.rs
@@ -123,7 +123,7 @@ impl CalculatorSyncHandler for CalculatorServer {
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),
+ what_op: Some(op.into()),
why: Some("no operands specified".to_owned()),
})
} else {
@@ -131,19 +131,26 @@ impl CalculatorSyncHandler for CalculatorServer {
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 => {
+ 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),
+ what_op: Some(op.into()),
why: Some("divide by 0".to_owned()),
})
} else {
Ok(num1 / num2)
}
+ },
+ _ => {
+ let op_val: i32 = op.into();
+ Err(InvalidOperation {
+ what_op: Some(op_val),
+ why: Some(format!("unsupported operation type '{}'", op_val)),
+ })
}
}
}