summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan Lozano <ivanlozano@google.com>2020-11-05 19:23:18 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2020-11-05 19:23:18 +0000
commitc70e5c7848cd7903c054b6e786d505295e8305c0 (patch)
treee327cafa4b052576b6094ec8f789657193c83767
parente176dc85c8c71670937948ec0ef1955a61a05379 (diff)
parent7d2f75e2f6df3106073ac1c4a8646699d59bdaf1 (diff)
downloadgrpcio-compiler-c70e5c7848cd7903c054b6e786d505295e8305c0.tar.gz
Initial import of grpcio-compiler. am: 166a6f003e am: 621c1e767f am: 9b66396c01 am: 7d2f75e2f6
Original change: https://android-review.googlesource.com/c/platform/external/rust/crates/grpcio-compiler/+/1486596 Change-Id: Ie69deacbf717fd205d98a31031bef49ecc1d5744
-rw-r--r--.cargo_vcs_info.json5
-rw-r--r--Cargo.lock340
-rw-r--r--Cargo.toml56
-rw-r--r--Cargo.toml.orig29
-rw-r--r--src/bin/grpc_rust_plugin.rs30
-rw-r--r--src/codegen.rs708
-rw-r--r--src/lib.rs8
-rw-r--r--src/prost_codegen.rs538
-rw-r--r--src/util.rs155
9 files changed, 1869 insertions, 0 deletions
diff --git a/.cargo_vcs_info.json b/.cargo_vcs_info.json
new file mode 100644
index 0000000..90abfbc
--- /dev/null
+++ b/.cargo_vcs_info.json
@@ -0,0 +1,5 @@
+{
+ "git": {
+ "sha1": "19a53b5cc9612511dabb365dae05286e807668e5"
+ }
+}
diff --git a/Cargo.lock b/Cargo.lock
new file mode 100644
index 0000000..ce8033e
--- /dev/null
+++ b/Cargo.lock
@@ -0,0 +1,340 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "anyhow"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f"
+
+[[package]]
+name = "autocfg"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
+
+[[package]]
+name = "bytes"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
+
+[[package]]
+name = "cfg-if"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
+
+[[package]]
+name = "derive-new"
+version = "0.5.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71f31892cd5c62e414316f2963c5689242c43d8e7bbcaaeca97e5e28c95d91d9"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "either"
+version = "1.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
+
+[[package]]
+name = "fixedbitset"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37ab347416e802de484e4d03c7316c48f1ecb56574dfd4a46a80f173ce1de04d"
+
+[[package]]
+name = "getrandom"
+version = "0.1.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "wasi",
+]
+
+[[package]]
+name = "grpcio-compiler"
+version = "0.6.0"
+dependencies = [
+ "derive-new",
+ "prost",
+ "prost-build",
+ "prost-types",
+ "protobuf",
+ "tempfile",
+]
+
+[[package]]
+name = "heck"
+version = "0.3.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
+dependencies = [
+ "unicode-segmentation",
+]
+
+[[package]]
+name = "indexmap"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "itertools"
+version = "0.8.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f56a2d0bc861f9165be4eb3442afd3c236d8a98afd426f65d92324ae1091a484"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.71"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
+
+[[package]]
+name = "log"
+version = "0.4.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
+dependencies = [
+ "cfg-if",
+]
+
+[[package]]
+name = "multimap"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8883adfde9756c1d30b0f519c9b8c502a94b41ac62f696453c37c7fc0a958ce"
+
+[[package]]
+name = "petgraph"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "467d164a6de56270bd7c4d070df81d07beace25012d5103ced4e9ff08d6afdb7"
+dependencies = [
+ "fixedbitset",
+ "indexmap",
+]
+
+[[package]]
+name = "ppv-lite86"
+version = "0.2.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
+dependencies = [
+ "unicode-xid",
+]
+
+[[package]]
+name = "prost"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce49aefe0a6144a45de32927c77bd2859a5f7677b55f220ae5b744e87389c212"
+dependencies = [
+ "bytes",
+ "prost-derive",
+]
+
+[[package]]
+name = "prost-build"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02b10678c913ecbd69350e8535c3aef91a8676c0773fc1d7b95cdd196d7f2f26"
+dependencies = [
+ "bytes",
+ "heck",
+ "itertools",
+ "log",
+ "multimap",
+ "petgraph",
+ "prost",
+ "prost-types",
+ "tempfile",
+ "which",
+]
+
+[[package]]
+name = "prost-derive"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "537aa19b95acde10a12fec4301466386f757403de4cd4e5b4fa78fb5ecb18f72"
+dependencies = [
+ "anyhow",
+ "itertools",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "prost-types"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1834f67c0697c001304b75be76f67add9c89742eda3a085ad8ee0bb38c3417aa"
+dependencies = [
+ "bytes",
+ "prost",
+]
+
+[[package]]
+name = "protobuf"
+version = "2.14.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e86d370532557ae7573551a1ec8235a0f8d6cb276c7c9e6aa490b511c447485"
+
+[[package]]
+name = "quote"
+version = "1.0.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "rand"
+version = "0.7.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
+dependencies = [
+ "getrandom",
+ "libc",
+ "rand_chacha",
+ "rand_core",
+ "rand_hc",
+]
+
+[[package]]
+name = "rand_chacha"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
+dependencies = [
+ "ppv-lite86",
+ "rand_core",
+]
+
+[[package]]
+name = "rand_core"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
+dependencies = [
+ "getrandom",
+]
+
+[[package]]
+name = "rand_hc"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
+dependencies = [
+ "rand_core",
+]
+
+[[package]]
+name = "redox_syscall"
+version = "0.1.56"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
+
+[[package]]
+name = "remove_dir_all"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
+dependencies = [
+ "winapi",
+]
+
+[[package]]
+name = "syn"
+version = "1.0.31"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5304cfdf27365b7585c25d4af91b35016ed21ef88f17ced89c7093b43dba8b6"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
+]
+
+[[package]]
+name = "tempfile"
+version = "3.1.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "rand",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi",
+]
+
+[[package]]
+name = "unicode-segmentation"
+version = "1.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
+
+[[package]]
+name = "unicode-xid"
+version = "0.2.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
+
+[[package]]
+name = "wasi"
+version = "0.9.0+wasi-snapshot-preview1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
+
+[[package]]
+name = "which"
+version = "3.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d011071ae14a2f6671d0b74080ae0cd8ebf3a6f8c9589a2cd45f23126fe29724"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "winapi"
+version = "0.3.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
+dependencies = [
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
+]
+
+[[package]]
+name = "winapi-i686-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
+
+[[package]]
+name = "winapi-x86_64-pc-windows-gnu"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
diff --git a/Cargo.toml b/Cargo.toml
new file mode 100644
index 0000000..cecb7aa
--- /dev/null
+++ b/Cargo.toml
@@ -0,0 +1,56 @@
+# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
+#
+# When uploading crates to the registry Cargo will automatically
+# "normalize" Cargo.toml files for maximal compatibility
+# with all versions of Cargo and also rewrite `path` dependencies
+# to registry (e.g., crates.io) dependencies
+#
+# If you believe there's an error in this file please file an
+# issue against the rust-lang/cargo repository. If you're
+# editing this file be aware that the upstream Cargo.toml
+# will likely look very different (and much more reasonable)
+
+[package]
+edition = "2018"
+name = "grpcio-compiler"
+version = "0.6.0"
+authors = ["The TiKV Project Developers"]
+description = "gRPC compiler for grpcio"
+homepage = "https://github.com/tikv/grpc-rs"
+documentation = "https://docs.rs/grpcio-compiler"
+keywords = ["compiler", "grpc", "protobuf"]
+categories = ["network-programming"]
+license = "Apache-2.0"
+repository = "https://github.com/tikv/grpc-rs"
+
+[[bin]]
+name = "grpc_rust_plugin"
+required-features = ["protobuf-codec"]
+[dependencies.derive-new]
+version = "0.5"
+optional = true
+
+[dependencies.prost]
+version = "0.6"
+optional = true
+
+[dependencies.prost-build]
+version = "0.6"
+optional = true
+
+[dependencies.prost-types]
+version = "0.6"
+optional = true
+
+[dependencies.protobuf]
+version = "2"
+optional = true
+
+[dependencies.tempfile]
+version = "3.0"
+optional = true
+
+[features]
+default = ["protobuf-codec"]
+prost-codec = ["prost-build", "prost-types", "prost", "derive-new", "tempfile"]
+protobuf-codec = ["protobuf"]
diff --git a/Cargo.toml.orig b/Cargo.toml.orig
new file mode 100644
index 0000000..07731e0
--- /dev/null
+++ b/Cargo.toml.orig
@@ -0,0 +1,29 @@
+[package]
+name = "grpcio-compiler"
+version = "0.6.0"
+edition = "2018"
+authors = ["The TiKV Project Developers"]
+license = "Apache-2.0"
+keywords = ["compiler", "grpc", "protobuf"]
+repository = "https://github.com/tikv/grpc-rs"
+homepage = "https://github.com/tikv/grpc-rs"
+documentation = "https://docs.rs/grpcio-compiler"
+description = "gRPC compiler for grpcio"
+categories = ["network-programming"]
+
+[features]
+default = ["protobuf-codec"]
+protobuf-codec = ["protobuf"]
+prost-codec = ["prost-build", "prost-types", "prost", "derive-new", "tempfile"]
+
+[dependencies]
+protobuf = { version = "2", optional = true }
+prost = { version = "0.6", optional = true }
+prost-build = { version = "0.6", optional = true }
+prost-types = { version = "0.6", optional = true }
+derive-new = { version = "0.5", optional = true }
+tempfile = { version = "3.0", optional = true }
+
+[[bin]]
+name = "grpc_rust_plugin"
+required-features = ["protobuf-codec"]
diff --git a/src/bin/grpc_rust_plugin.rs b/src/bin/grpc_rust_plugin.rs
new file mode 100644
index 0000000..7d46238
--- /dev/null
+++ b/src/bin/grpc_rust_plugin.rs
@@ -0,0 +1,30 @@
+// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
+
+// Copyright (c) 2016, Stepan Koltsov
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+extern crate grpcio_compiler;
+
+use grpcio_compiler::codegen;
+
+fn main() {
+ codegen::protoc_gen_grpc_rust_main();
+}
diff --git a/src/codegen.rs b/src/codegen.rs
new file mode 100644
index 0000000..13a6a21
--- /dev/null
+++ b/src/codegen.rs
@@ -0,0 +1,708 @@
+// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
+
+// Copyright (c) 2016, Stepan Koltsov
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+use std::collections::HashMap;
+use std::io::Write;
+
+use protobuf::compiler_plugin;
+use protobuf::descriptor::*;
+use protobuf::descriptorx::*;
+
+struct CodeWriter<'a> {
+ writer: &'a mut (dyn Write + 'a),
+ indent: String,
+}
+
+impl<'a> CodeWriter<'a> {
+ pub fn new(writer: &'a mut dyn Write) -> CodeWriter<'a> {
+ CodeWriter {
+ writer,
+ indent: "".to_string(),
+ }
+ }
+
+ pub fn write_line<S: AsRef<str>>(&mut self, line: S) {
+ (if line.as_ref().is_empty() {
+ self.writer.write_all(b"\n")
+ } else {
+ let s: String = [self.indent.as_ref(), line.as_ref(), "\n"].concat();
+ self.writer.write_all(s.as_bytes())
+ })
+ .unwrap();
+ }
+
+ pub fn write_generated(&mut self) {
+ self.write_line("// This file is generated. Do not edit");
+ self.write_generated_common();
+ }
+
+ fn write_generated_common(&mut self) {
+ // https://secure.phabricator.com/T784
+ self.write_line("// @generated");
+
+ self.write_line("");
+ self.comment("https://github.com/Manishearth/rust-clippy/issues/702");
+ self.write_line("#![allow(unknown_lints)]");
+ self.write_line("#![allow(clippy::all)]");
+ self.write_line("");
+ self.write_line("#![cfg_attr(rustfmt, rustfmt_skip)]");
+ self.write_line("");
+ self.write_line("#![allow(box_pointers)]");
+ self.write_line("#![allow(dead_code)]");
+ self.write_line("#![allow(missing_docs)]");
+ self.write_line("#![allow(non_camel_case_types)]");
+ self.write_line("#![allow(non_snake_case)]");
+ self.write_line("#![allow(non_upper_case_globals)]");
+ self.write_line("#![allow(trivial_casts)]");
+ self.write_line("#![allow(unsafe_code)]");
+ self.write_line("#![allow(unused_imports)]");
+ self.write_line("#![allow(unused_results)]");
+ }
+
+ pub fn indented<F>(&mut self, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ cb(&mut CodeWriter {
+ writer: self.writer,
+ indent: format!("{} ", self.indent),
+ });
+ }
+
+ #[allow(dead_code)]
+ pub fn commented<F>(&mut self, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ cb(&mut CodeWriter {
+ writer: self.writer,
+ indent: format!("// {}", self.indent),
+ });
+ }
+
+ pub fn block<F>(&mut self, first_line: &str, last_line: &str, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.write_line(first_line);
+ self.indented(cb);
+ self.write_line(last_line);
+ }
+
+ pub fn expr_block<F>(&mut self, prefix: &str, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.block(&format!("{} {{", prefix), "}", cb);
+ }
+
+ pub fn impl_self_block<S: AsRef<str>, F>(&mut self, name: S, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.expr_block(&format!("impl {}", name.as_ref()), cb);
+ }
+
+ pub fn pub_struct<S: AsRef<str>, F>(&mut self, name: S, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.expr_block(&format!("pub struct {}", name.as_ref()), cb);
+ }
+
+ pub fn pub_trait<F>(&mut self, name: &str, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.expr_block(&format!("pub trait {}", name), cb);
+ }
+
+ pub fn field_entry(&mut self, name: &str, value: &str) {
+ self.write_line(&format!("{}: {},", name, value));
+ }
+
+ pub fn field_decl(&mut self, name: &str, field_type: &str) {
+ self.write_line(&format!("{}: {},", name, field_type));
+ }
+
+ pub fn comment(&mut self, comment: &str) {
+ if comment.is_empty() {
+ self.write_line("//");
+ } else {
+ self.write_line(&format!("// {}", comment));
+ }
+ }
+
+ pub fn fn_def(&mut self, sig: &str) {
+ self.write_line(&format!("fn {};", sig));
+ }
+
+ pub fn fn_block<F>(&mut self, public: bool, sig: &str, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ if public {
+ self.expr_block(&format!("pub fn {}", sig), cb);
+ } else {
+ self.expr_block(&format!("fn {}", sig), cb);
+ }
+ }
+
+ pub fn pub_fn<F>(&mut self, sig: &str, cb: F)
+ where
+ F: Fn(&mut CodeWriter),
+ {
+ self.fn_block(true, sig, cb);
+ }
+}
+
+use super::util::{self, fq_grpc, to_snake_case, MethodType};
+
+struct MethodGen<'a> {
+ proto: &'a MethodDescriptorProto,
+ service_name: String,
+ service_path: String,
+ root_scope: &'a RootScope<'a>,
+}
+
+impl<'a> MethodGen<'a> {
+ fn new(
+ proto: &'a MethodDescriptorProto,
+ service_name: String,
+ service_path: String,
+ root_scope: &'a RootScope<'a>,
+ ) -> MethodGen<'a> {
+ MethodGen {
+ proto,
+ service_name,
+ service_path,
+ root_scope,
+ }
+ }
+
+ fn input(&self) -> String {
+ format!(
+ "super::{}",
+ self.root_scope
+ .find_message(self.proto.get_input_type())
+ .rust_fq_name()
+ )
+ }
+
+ fn output(&self) -> String {
+ format!(
+ "super::{}",
+ self.root_scope
+ .find_message(self.proto.get_output_type())
+ .rust_fq_name()
+ )
+ }
+
+ fn method_type(&self) -> (MethodType, String) {
+ match (
+ self.proto.get_client_streaming(),
+ self.proto.get_server_streaming(),
+ ) {
+ (false, false) => (MethodType::Unary, fq_grpc("MethodType::Unary")),
+ (true, false) => (
+ MethodType::ClientStreaming,
+ fq_grpc("MethodType::ClientStreaming"),
+ ),
+ (false, true) => (
+ MethodType::ServerStreaming,
+ fq_grpc("MethodType::ServerStreaming"),
+ ),
+ (true, true) => (MethodType::Duplex, fq_grpc("MethodType::Duplex")),
+ }
+ }
+
+ fn service_name(&self) -> String {
+ to_snake_case(&self.service_name)
+ }
+
+ fn name(&self) -> String {
+ to_snake_case(self.proto.get_name())
+ }
+
+ fn fq_name(&self) -> String {
+ format!("\"{}/{}\"", self.service_path, &self.proto.get_name())
+ }
+
+ fn const_method_name(&self) -> String {
+ format!(
+ "METHOD_{}_{}",
+ self.service_name().to_uppercase(),
+ self.name().to_uppercase()
+ )
+ }
+
+ fn write_definition(&self, w: &mut CodeWriter) {
+ let head = format!(
+ "const {}: {}<{}, {}> = {} {{",
+ self.const_method_name(),
+ fq_grpc("Method"),
+ self.input(),
+ self.output(),
+ fq_grpc("Method")
+ );
+ let pb_mar = format!(
+ "{} {{ ser: {}, de: {} }}",
+ fq_grpc("Marshaller"),
+ fq_grpc("pb_ser"),
+ fq_grpc("pb_de")
+ );
+ w.block(&head, "};", |w| {
+ w.field_entry("ty", &self.method_type().1);
+ w.field_entry("name", &self.fq_name());
+ w.field_entry("req_mar", &pb_mar);
+ w.field_entry("resp_mar", &pb_mar);
+ });
+ }
+
+ // Method signatures
+ fn unary(&self, method_name: &str) -> String {
+ format!(
+ "{}(&self, req: &{}) -> {}<{}>",
+ method_name,
+ self.input(),
+ fq_grpc("Result"),
+ self.output()
+ )
+ }
+
+ fn unary_opt(&self, method_name: &str) -> String {
+ format!(
+ "{}_opt(&self, req: &{}, opt: {}) -> {}<{}>",
+ method_name,
+ self.input(),
+ fq_grpc("CallOption"),
+ fq_grpc("Result"),
+ self.output()
+ )
+ }
+
+ fn unary_async(&self, method_name: &str) -> String {
+ format!(
+ "{}_async(&self, req: &{}) -> {}<{}<{}>>",
+ method_name,
+ self.input(),
+ fq_grpc("Result"),
+ fq_grpc("ClientUnaryReceiver"),
+ self.output()
+ )
+ }
+
+ fn unary_async_opt(&self, method_name: &str) -> String {
+ format!(
+ "{}_async_opt(&self, req: &{}, opt: {}) -> {}<{}<{}>>",
+ method_name,
+ self.input(),
+ fq_grpc("CallOption"),
+ fq_grpc("Result"),
+ fq_grpc("ClientUnaryReceiver"),
+ self.output()
+ )
+ }
+
+ fn client_streaming(&self, method_name: &str) -> String {
+ format!(
+ "{}(&self) -> {}<({}<{}>, {}<{}>)>",
+ method_name,
+ fq_grpc("Result"),
+ fq_grpc("ClientCStreamSender"),
+ self.input(),
+ fq_grpc("ClientCStreamReceiver"),
+ self.output()
+ )
+ }
+
+ fn client_streaming_opt(&self, method_name: &str) -> String {
+ format!(
+ "{}_opt(&self, opt: {}) -> {}<({}<{}>, {}<{}>)>",
+ method_name,
+ fq_grpc("CallOption"),
+ fq_grpc("Result"),
+ fq_grpc("ClientCStreamSender"),
+ self.input(),
+ fq_grpc("ClientCStreamReceiver"),
+ self.output()
+ )
+ }
+
+ fn server_streaming(&self, method_name: &str) -> String {
+ format!(
+ "{}(&self, req: &{}) -> {}<{}<{}>>",
+ method_name,
+ self.input(),
+ fq_grpc("Result"),
+ fq_grpc("ClientSStreamReceiver"),
+ self.output()
+ )
+ }
+
+ fn server_streaming_opt(&self, method_name: &str) -> String {
+ format!(
+ "{}_opt(&self, req: &{}, opt: {}) -> {}<{}<{}>>",
+ method_name,
+ self.input(),
+ fq_grpc("CallOption"),
+ fq_grpc("Result"),
+ fq_grpc("ClientSStreamReceiver"),
+ self.output()
+ )
+ }
+
+ fn duplex_streaming(&self, method_name: &str) -> String {
+ format!(
+ "{}(&self) -> {}<({}<{}>, {}<{}>)>",
+ method_name,
+ fq_grpc("Result"),
+ fq_grpc("ClientDuplexSender"),
+ self.input(),
+ fq_grpc("ClientDuplexReceiver"),
+ self.output()
+ )
+ }
+
+ fn duplex_streaming_opt(&self, method_name: &str) -> String {
+ format!(
+ "{}_opt(&self, opt: {}) -> {}<({}<{}>, {}<{}>)>",
+ method_name,
+ fq_grpc("CallOption"),
+ fq_grpc("Result"),
+ fq_grpc("ClientDuplexSender"),
+ self.input(),
+ fq_grpc("ClientDuplexReceiver"),
+ self.output()
+ )
+ }
+
+ fn write_client(&self, w: &mut CodeWriter) {
+ let method_name = self.name();
+ match self.method_type().0 {
+ // Unary
+ MethodType::Unary => {
+ w.pub_fn(&self.unary_opt(&method_name), |w| {
+ w.write_line(&format!(
+ "self.client.unary_call(&{}, req, opt)",
+ self.const_method_name()
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.unary(&method_name), |w| {
+ w.write_line(&format!(
+ "self.{}_opt(req, {})",
+ method_name,
+ fq_grpc("CallOption::default()")
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.unary_async_opt(&method_name), |w| {
+ w.write_line(&format!(
+ "self.client.unary_call_async(&{}, req, opt)",
+ self.const_method_name()
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.unary_async(&method_name), |w| {
+ w.write_line(&format!(
+ "self.{}_async_opt(req, {})",
+ method_name,
+ fq_grpc("CallOption::default()")
+ ));
+ });
+ }
+
+ // Client streaming
+ MethodType::ClientStreaming => {
+ w.pub_fn(&self.client_streaming_opt(&method_name), |w| {
+ w.write_line(&format!(
+ "self.client.client_streaming(&{}, opt)",
+ self.const_method_name()
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.client_streaming(&method_name), |w| {
+ w.write_line(&format!(
+ "self.{}_opt({})",
+ method_name,
+ fq_grpc("CallOption::default()")
+ ));
+ });
+ }
+
+ // Server streaming
+ MethodType::ServerStreaming => {
+ w.pub_fn(&self.server_streaming_opt(&method_name), |w| {
+ w.write_line(&format!(
+ "self.client.server_streaming(&{}, req, opt)",
+ self.const_method_name()
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.server_streaming(&method_name), |w| {
+ w.write_line(&format!(
+ "self.{}_opt(req, {})",
+ method_name,
+ fq_grpc("CallOption::default()")
+ ));
+ });
+ }
+
+ // Duplex streaming
+ MethodType::Duplex => {
+ w.pub_fn(&self.duplex_streaming_opt(&method_name), |w| {
+ w.write_line(&format!(
+ "self.client.duplex_streaming(&{}, opt)",
+ self.const_method_name()
+ ));
+ });
+ w.write_line("");
+
+ w.pub_fn(&self.duplex_streaming(&method_name), |w| {
+ w.write_line(&format!(
+ "self.{}_opt({})",
+ method_name,
+ fq_grpc("CallOption::default()")
+ ));
+ });
+ }
+ };
+ }
+
+ fn write_service(&self, w: &mut CodeWriter) {
+ let req_stream_type = format!("{}<{}>", fq_grpc("RequestStream"), self.input());
+ let (req, req_type, resp_type) = match self.method_type().0 {
+ MethodType::Unary => ("req", self.input(), "UnarySink"),
+ MethodType::ClientStreaming => ("stream", req_stream_type, "ClientStreamingSink"),
+ MethodType::ServerStreaming => ("req", self.input(), "ServerStreamingSink"),
+ MethodType::Duplex => ("stream", req_stream_type, "DuplexSink"),
+ };
+ let sig = format!(
+ "{}(&mut self, ctx: {}, {}: {}, sink: {}<{}>)",
+ self.name(),
+ fq_grpc("RpcContext"),
+ req,
+ req_type,
+ fq_grpc(resp_type),
+ self.output()
+ );
+ w.fn_def(&sig);
+ }
+
+ fn write_bind(&self, w: &mut CodeWriter) {
+ let add = match self.method_type().0 {
+ MethodType::Unary => "add_unary_handler",
+ MethodType::ClientStreaming => "add_client_streaming_handler",
+ MethodType::ServerStreaming => "add_server_streaming_handler",
+ MethodType::Duplex => "add_duplex_streaming_handler",
+ };
+ w.block(
+ &format!(
+ "builder = builder.{}(&{}, move |ctx, req, resp| {{",
+ add,
+ self.const_method_name()
+ ),
+ "});",
+ |w| {
+ w.write_line(&format!("instance.{}(ctx, req, resp)", self.name()));
+ },
+ );
+ }
+}
+
+struct ServiceGen<'a> {
+ proto: &'a ServiceDescriptorProto,
+ methods: Vec<MethodGen<'a>>,
+}
+
+impl<'a> ServiceGen<'a> {
+ fn new(
+ proto: &'a ServiceDescriptorProto,
+ file: &FileDescriptorProto,
+ root_scope: &'a RootScope,
+ ) -> ServiceGen<'a> {
+ let service_path = if file.get_package().is_empty() {
+ format!("/{}", proto.get_name())
+ } else {
+ format!("/{}.{}", file.get_package(), proto.get_name())
+ };
+ let methods = proto
+ .get_method()
+ .iter()
+ .map(|m| {
+ MethodGen::new(
+ m,
+ util::to_camel_case(proto.get_name()),
+ service_path.clone(),
+ root_scope,
+ )
+ })
+ .collect();
+
+ ServiceGen { proto, methods }
+ }
+
+ fn service_name(&self) -> String {
+ util::to_camel_case(self.proto.get_name())
+ }
+
+ fn client_name(&self) -> String {
+ format!("{}Client", self.service_name())
+ }
+
+ fn write_client(&self, w: &mut CodeWriter) {
+ w.write_line("#[derive(Clone)]");
+ w.pub_struct(&self.client_name(), |w| {
+ w.field_decl("client", "::grpcio::Client");
+ });
+
+ w.write_line("");
+
+ w.impl_self_block(&self.client_name(), |w| {
+ w.pub_fn("new(channel: ::grpcio::Channel) -> Self", |w| {
+ w.expr_block(&self.client_name(), |w| {
+ w.field_entry("client", "::grpcio::Client::new(channel)");
+ });
+ });
+
+ for method in &self.methods {
+ w.write_line("");
+ method.write_client(w);
+ }
+ w.pub_fn(
+ "spawn<F>(&self, f: F) where F: ::futures::Future<Output = ()> + Send + 'static",
+ |w| {
+ w.write_line("self.client.spawn(f)");
+ },
+ )
+ });
+ }
+
+ fn write_server(&self, w: &mut CodeWriter) {
+ w.pub_trait(&self.service_name(), |w| {
+ for method in &self.methods {
+ method.write_service(w);
+ }
+ });
+
+ w.write_line("");
+
+ let s = format!(
+ "create_{}<S: {} + Send + Clone + 'static>(s: S) -> {}",
+ to_snake_case(&self.service_name()),
+ self.service_name(),
+ fq_grpc("Service")
+ );
+ w.pub_fn(&s, |w| {
+ w.write_line("let mut builder = ::grpcio::ServiceBuilder::new();");
+ for method in &self.methods[0..self.methods.len() - 1] {
+ w.write_line("let mut instance = s.clone();");
+ method.write_bind(w);
+ }
+
+ w.write_line("let mut instance = s;");
+ self.methods[self.methods.len() - 1].write_bind(w);
+
+ w.write_line("builder.build()");
+ });
+ }
+
+ fn write_method_definitions(&self, w: &mut CodeWriter) {
+ for (i, method) in self.methods.iter().enumerate() {
+ if i != 0 {
+ w.write_line("");
+ }
+
+ method.write_definition(w);
+ }
+ }
+
+ fn write(&self, w: &mut CodeWriter) {
+ self.write_method_definitions(w);
+ w.write_line("");
+ self.write_client(w);
+ w.write_line("");
+ self.write_server(w);
+ }
+}
+
+fn gen_file(
+ file: &FileDescriptorProto,
+ root_scope: &RootScope,
+) -> Option<compiler_plugin::GenResult> {
+ if file.get_service().is_empty() {
+ return None;
+ }
+
+ let base = protobuf::descriptorx::proto_path_to_rust_mod(file.get_name());
+
+ let mut v = Vec::new();
+ {
+ let mut w = CodeWriter::new(&mut v);
+ w.write_generated();
+
+ for service in file.get_service() {
+ w.write_line("");
+ ServiceGen::new(service, file, root_scope).write(&mut w);
+ }
+ }
+
+ Some(compiler_plugin::GenResult {
+ name: base + "_grpc.rs",
+ content: v,
+ })
+}
+
+pub fn gen(
+ file_descriptors: &[FileDescriptorProto],
+ files_to_generate: &[String],
+) -> Vec<compiler_plugin::GenResult> {
+ let files_map: HashMap<&str, &FileDescriptorProto> =
+ file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
+
+ let root_scope = RootScope { file_descriptors };
+
+ let mut results = Vec::new();
+
+ for file_name in files_to_generate {
+ let file = files_map[&file_name[..]];
+
+ if file.get_service().is_empty() {
+ continue;
+ }
+
+ results.extend(gen_file(file, &root_scope).into_iter());
+ }
+
+ results
+}
+
+pub fn protoc_gen_grpc_rust_main() {
+ compiler_plugin::plugin_main(gen);
+}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..3c7052b
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,8 @@
+// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
+
+#[cfg(feature = "protobuf-codec")]
+pub mod codegen;
+#[cfg(feature = "prost-codec")]
+pub mod prost_codegen;
+
+mod util;
diff --git a/src/prost_codegen.rs b/src/prost_codegen.rs
new file mode 100644
index 0000000..e2d7533
--- /dev/null
+++ b/src/prost_codegen.rs
@@ -0,0 +1,538 @@
+// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
+
+use std::io::{Error, ErrorKind, Read};
+use std::path::Path;
+use std::{fs, io, process::Command};
+
+use derive_new::new;
+use prost::Message;
+use prost_build::{protoc, protoc_include, Config, Method, Service, ServiceGenerator};
+use prost_types::FileDescriptorSet;
+
+use crate::util::{fq_grpc, to_snake_case, MethodType};
+
+/// Returns the names of all packages compiled.
+pub fn compile_protos<P>(protos: &[P], includes: &[P], out_dir: &str) -> io::Result<Vec<String>>
+where
+ P: AsRef<Path>,
+{
+ let mut prost_config = Config::new();
+ prost_config.service_generator(Box::new(Generator));
+ prost_config.out_dir(out_dir);
+
+ // Create a file descriptor set for the protocol files.
+ let tmp = tempfile::Builder::new().prefix("prost-build").tempdir()?;
+ let descriptor_set = tmp.path().join("prost-descriptor-set");
+
+ let mut cmd = Command::new(protoc());
+ cmd.arg("--include_imports")
+ .arg("--include_source_info")
+ .arg("-o")
+ .arg(&descriptor_set);
+
+ for include in includes {
+ cmd.arg("-I").arg(include.as_ref());
+ }
+
+ // Set the protoc include after the user includes in case the user wants to
+ // override one of the built-in .protos.
+ cmd.arg("-I").arg(protoc_include());
+
+ for proto in protos {
+ cmd.arg(proto.as_ref());
+ }
+
+ let output = cmd.output()?;
+ if !output.status.success() {
+ return Err(Error::new(
+ ErrorKind::Other,
+ format!("protoc failed: {}", String::from_utf8_lossy(&output.stderr)),
+ ));
+ }
+
+ let mut buf = Vec::new();
+ fs::File::open(descriptor_set)?.read_to_end(&mut buf)?;
+ let descriptor_set = FileDescriptorSet::decode(buf.as_slice())?;
+
+ // Get the package names from the descriptor set.
+ let mut packages: Vec<_> = descriptor_set
+ .file
+ .iter()
+ .filter_map(|f| f.package.clone())
+ .collect();
+ packages.sort();
+ packages.dedup();
+
+ // FIXME(https://github.com/danburkert/prost/pull/155)
+ // Unfortunately we have to forget the above work and use `compile_protos` to
+ // actually generate the Rust code.
+ prost_config.compile_protos(protos, includes)?;
+
+ Ok(packages)
+}
+
+struct Generator;
+
+impl ServiceGenerator for Generator {
+ fn generate(&mut self, service: Service, buf: &mut String) {
+ generate_methods(&service, buf);
+ generate_client(&service, buf);
+ generate_server(&service, buf);
+ }
+}
+
+fn generate_methods(service: &Service, buf: &mut String) {
+ let service_path = if service.package.is_empty() {
+ format!("/{}", service.proto_name)
+ } else {
+ format!("/{}.{}", service.package, service.proto_name)
+ };
+
+ for method in &service.methods {
+ generate_method(&service.name, &service_path, method, buf);
+ }
+}
+
+fn const_method_name(service_name: &str, method: &Method) -> String {
+ format!(
+ "METHOD_{}_{}",
+ to_snake_case(service_name).to_uppercase(),
+ method.name.to_uppercase()
+ )
+}
+
+fn generate_method(service_name: &str, service_path: &str, method: &Method, buf: &mut String) {
+ let name = const_method_name(service_name, method);
+ let ty = format!(
+ "{}<{}, {}>",
+ fq_grpc("Method"),
+ method.input_type,
+ method.output_type
+ );
+
+ buf.push_str("const ");
+ buf.push_str(&name);
+ buf.push_str(": ");
+ buf.push_str(&ty);
+ buf.push_str(" = ");
+ generate_method_body(service_path, method, buf);
+}
+
+fn generate_method_body(service_path: &str, method: &Method, buf: &mut String) {
+ let ty = fq_grpc(&MethodType::from_method(method).to_string());
+ let pr_mar = format!(
+ "{} {{ ser: {}, de: {} }}",
+ fq_grpc("Marshaller"),
+ fq_grpc("pr_ser"),
+ fq_grpc("pr_de")
+ );
+
+ buf.push_str(&fq_grpc("Method"));
+ buf.push('{');
+ generate_field_init("ty", &ty, buf);
+ generate_field_init(
+ "name",
+ &format!("\"{}/{}\"", service_path, method.proto_name),
+ buf,
+ );
+ generate_field_init("req_mar", &pr_mar, buf);
+ generate_field_init("resp_mar", &pr_mar, buf);
+ buf.push_str("};\n");
+}
+
+// TODO share this code with protobuf codegen
+impl MethodType {
+ fn from_method(method: &Method) -> MethodType {
+ match (method.client_streaming, method.server_streaming) {
+ (false, false) => MethodType::Unary,
+ (true, false) => MethodType::ClientStreaming,
+ (false, true) => MethodType::ServerStreaming,
+ (true, true) => MethodType::Duplex,
+ }
+ }
+}
+
+fn generate_field_init(name: &str, value: &str, buf: &mut String) {
+ buf.push_str(name);
+ buf.push_str(": ");
+ buf.push_str(value);
+ buf.push_str(", ");
+}
+
+fn generate_client(service: &Service, buf: &mut String) {
+ let client_name = format!("{}Client", service.name);
+ buf.push_str("#[derive(Clone)]\n");
+ buf.push_str("pub struct ");
+ buf.push_str(&client_name);
+ buf.push_str(" { client: ::grpcio::Client }\n");
+
+ buf.push_str("impl ");
+ buf.push_str(&client_name);
+ buf.push_str(" {\n");
+ generate_ctor(&client_name, buf);
+ generate_client_methods(service, buf);
+ generate_spawn(buf);
+ buf.push_str("}\n")
+}
+
+fn generate_ctor(client_name: &str, buf: &mut String) {
+ buf.push_str("pub fn new(channel: ::grpcio::Channel) -> Self { ");
+ buf.push_str(client_name);
+ buf.push_str(" { client: ::grpcio::Client::new(channel) }");
+ buf.push_str("}\n");
+}
+
+fn generate_client_methods(service: &Service, buf: &mut String) {
+ for method in &service.methods {
+ generate_client_method(&service.name, method, buf);
+ }
+}
+
+fn generate_client_method(service_name: &str, method: &Method, buf: &mut String) {
+ let name = &format!(
+ "METHOD_{}_{}",
+ to_snake_case(service_name).to_uppercase(),
+ method.name.to_uppercase()
+ );
+ match MethodType::from_method(method) {
+ MethodType::Unary => {
+ ClientMethod::new(
+ &method.name,
+ true,
+ Some(&method.input_type),
+ false,
+ vec![&method.output_type],
+ "unary_call",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ false,
+ Some(&method.input_type),
+ false,
+ vec![&method.output_type],
+ "unary_call",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ true,
+ Some(&method.input_type),
+ true,
+ vec![&format!(
+ "{}<{}>",
+ fq_grpc("ClientUnaryReceiver"),
+ method.output_type
+ )],
+ "unary_call",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ false,
+ Some(&method.input_type),
+ true,
+ vec![&format!(
+ "{}<{}>",
+ fq_grpc("ClientUnaryReceiver"),
+ method.output_type
+ )],
+ "unary_call",
+ name,
+ )
+ .generate(buf);
+ }
+ MethodType::ClientStreaming => {
+ ClientMethod::new(
+ &method.name,
+ true,
+ None,
+ false,
+ vec![
+ &format!("{}<{}>", fq_grpc("ClientCStreamSender"), method.input_type),
+ &format!(
+ "{}<{}>",
+ fq_grpc("ClientCStreamReceiver"),
+ method.output_type
+ ),
+ ],
+ "client_streaming",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ false,
+ None,
+ false,
+ vec![
+ &format!("{}<{}>", fq_grpc("ClientCStreamSender"), method.input_type),
+ &format!(
+ "{}<{}>",
+ fq_grpc("ClientCStreamReceiver"),
+ method.output_type
+ ),
+ ],
+ "client_streaming",
+ name,
+ )
+ .generate(buf);
+ }
+ MethodType::ServerStreaming => {
+ ClientMethod::new(
+ &method.name,
+ true,
+ Some(&method.input_type),
+ false,
+ vec![&format!(
+ "{}<{}>",
+ fq_grpc("ClientSStreamReceiver"),
+ method.output_type
+ )],
+ "server_streaming",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ false,
+ Some(&method.input_type),
+ false,
+ vec![&format!(
+ "{}<{}>",
+ fq_grpc("ClientSStreamReceiver"),
+ method.output_type
+ )],
+ "server_streaming",
+ name,
+ )
+ .generate(buf);
+ }
+ MethodType::Duplex => {
+ ClientMethod::new(
+ &method.name,
+ true,
+ None,
+ false,
+ vec![
+ &format!("{}<{}>", fq_grpc("ClientDuplexSender"), method.input_type),
+ &format!(
+ "{}<{}>",
+ fq_grpc("ClientDuplexReceiver"),
+ method.output_type
+ ),
+ ],
+ "duplex_streaming",
+ name,
+ )
+ .generate(buf);
+ ClientMethod::new(
+ &method.name,
+ false,
+ None,
+ false,
+ vec![
+ &format!("{}<{}>", fq_grpc("ClientDuplexSender"), method.input_type),
+ &format!(
+ "{}<{}>",
+ fq_grpc("ClientDuplexReceiver"),
+ method.output_type
+ ),
+ ],
+ "duplex_streaming",
+ name,
+ )
+ .generate(buf);
+ }
+ }
+}
+
+#[derive(new)]
+struct ClientMethod<'a> {
+ method_name: &'a str,
+ opt: bool,
+ request: Option<&'a str>,
+ r#async: bool,
+ result_types: Vec<&'a str>,
+ inner_method_name: &'a str,
+ data_name: &'a str,
+}
+
+impl<'a> ClientMethod<'a> {
+ fn generate(&self, buf: &mut String) {
+ buf.push_str("pub fn ");
+
+ buf.push_str(self.method_name);
+ if self.r#async {
+ buf.push_str("_async");
+ }
+ if self.opt {
+ buf.push_str("_opt");
+ }
+
+ buf.push_str("(&self");
+ if let Some(req) = self.request {
+ buf.push_str(", req: &");
+ buf.push_str(req);
+ }
+ if self.opt {
+ buf.push_str(", opt: ");
+ buf.push_str(&fq_grpc("CallOption"));
+ }
+ buf.push_str(") -> ");
+
+ buf.push_str(&fq_grpc("Result"));
+ buf.push('<');
+ if self.result_types.len() != 1 {
+ buf.push('(');
+ }
+ for rt in &self.result_types {
+ buf.push_str(rt);
+ buf.push(',');
+ }
+ if self.result_types.len() != 1 {
+ buf.push(')');
+ }
+ buf.push_str("> { ");
+ if self.opt {
+ self.generate_inner_body(buf);
+ } else {
+ self.generate_opt_body(buf);
+ }
+ buf.push_str(" }\n");
+ }
+
+ // Method delegates to the `_opt` version of the method.
+ fn generate_opt_body(&self, buf: &mut String) {
+ buf.push_str("self.");
+ buf.push_str(self.method_name);
+ if self.r#async {
+ buf.push_str("_async");
+ }
+ buf.push_str("_opt(");
+ if self.request.is_some() {
+ buf.push_str("req, ");
+ }
+ buf.push_str(&fq_grpc("CallOption::default()"));
+ buf.push(')');
+ }
+
+ // Method delegates to the inner client.
+ fn generate_inner_body(&self, buf: &mut String) {
+ buf.push_str("self.client.");
+ buf.push_str(self.inner_method_name);
+ if self.r#async {
+ buf.push_str("_async");
+ }
+ buf.push_str("(&");
+ buf.push_str(self.data_name);
+ if self.request.is_some() {
+ buf.push_str(", req");
+ }
+ buf.push_str(", opt)");
+ }
+}
+
+fn generate_spawn(buf: &mut String) {
+ buf.push_str(
+ "pub fn spawn<F>(&self, f: F) \
+ where F: ::futures::Future<Output = ()> + Send + 'static {\
+ self.client.spawn(f)\
+ }\n",
+ );
+}
+
+fn generate_server(service: &Service, buf: &mut String) {
+ buf.push_str("pub trait ");
+ buf.push_str(&service.name);
+ buf.push_str(" {\n");
+ generate_server_methods(service, buf);
+ buf.push_str("}\n");
+
+ buf.push_str("pub fn create_");
+ buf.push_str(&to_snake_case(&service.name));
+ buf.push_str("<S: ");
+ buf.push_str(&service.name);
+ buf.push_str(" + Send + Clone + 'static>(s: S) -> ");
+ buf.push_str(&fq_grpc("Service"));
+ buf.push_str(" {\n");
+ buf.push_str("let mut builder = ::grpcio::ServiceBuilder::new();\n");
+
+ for method in &service.methods[0..service.methods.len() - 1] {
+ buf.push_str("let mut instance = s.clone();\n");
+ generate_method_bind(&service.name, method, buf);
+ }
+
+ buf.push_str("let mut instance = s;\n");
+ generate_method_bind(
+ &service.name,
+ &service.methods[service.methods.len() - 1],
+ buf,
+ );
+
+ buf.push_str("builder.build()\n");
+ buf.push_str("}\n");
+}
+
+fn generate_server_methods(service: &Service, buf: &mut String) {
+ for method in &service.methods {
+ let method_type = MethodType::from_method(method);
+ let request_arg = match method_type {
+ MethodType::Unary | MethodType::ServerStreaming => {
+ format!("req: {}", method.input_type)
+ }
+ MethodType::ClientStreaming | MethodType::Duplex => format!(
+ "stream: {}<{}>",
+ fq_grpc("RequestStream"),
+ method.input_type
+ ),
+ };
+ let response_type = match method_type {
+ MethodType::Unary => "UnarySink",
+ MethodType::ClientStreaming => "ClientStreamingSink",
+ MethodType::ServerStreaming => "ServerStreamingSink",
+ MethodType::Duplex => "DuplexSink",
+ };
+ generate_server_method(method, &request_arg, response_type, buf);
+ }
+}
+
+fn generate_server_method(
+ method: &Method,
+ request_arg: &str,
+ response_type: &str,
+ buf: &mut String,
+) {
+ buf.push_str("fn ");
+ buf.push_str(&method.name);
+ buf.push_str("(&mut self, ctx: ");
+ buf.push_str(&fq_grpc("RpcContext"));
+ buf.push_str(", ");
+ buf.push_str(request_arg);
+ buf.push_str(", sink: ");
+ buf.push_str(&fq_grpc(response_type));
+ buf.push('<');
+ buf.push_str(&method.output_type);
+ buf.push('>');
+ buf.push_str(");\n");
+}
+
+fn generate_method_bind(service_name: &str, method: &Method, buf: &mut String) {
+ let add_name = match MethodType::from_method(method) {
+ MethodType::Unary => "add_unary_handler",
+ MethodType::ClientStreaming => "add_client_streaming_handler",
+ MethodType::ServerStreaming => "add_server_streaming_handler",
+ MethodType::Duplex => "add_duplex_streaming_handler",
+ };
+
+ buf.push_str("builder = builder.");
+ buf.push_str(add_name);
+ buf.push_str("(&");
+ buf.push_str(&const_method_name(service_name, method));
+ buf.push_str(", move |ctx, req, resp| instance.");
+ buf.push_str(&method.name);
+ buf.push_str("(ctx, req, resp));\n");
+}
diff --git a/src/util.rs b/src/util.rs
new file mode 100644
index 0000000..c46be2d
--- /dev/null
+++ b/src/util.rs
@@ -0,0 +1,155 @@
+// Copyright 2019 TiKV Project Authors. Licensed under Apache-2.0.
+
+use std::fmt;
+use std::str;
+
+// A struct that divide a name into serveral parts that meets rust's guidelines.
+struct NameSpliter<'a> {
+ name: &'a [u8],
+ pos: usize,
+}
+
+impl<'a> NameSpliter<'a> {
+ fn new(s: &str) -> NameSpliter {
+ NameSpliter {
+ name: s.as_bytes(),
+ pos: 0,
+ }
+ }
+}
+
+impl<'a> Iterator for NameSpliter<'a> {
+ type Item = &'a str;
+
+ fn next(&mut self) -> Option<&'a str> {
+ if self.pos == self.name.len() {
+ return None;
+ }
+ // skip all prefix '_'
+ while self.pos < self.name.len() && self.name[self.pos] == b'_' {
+ self.pos += 1;
+ }
+ let mut pos = self.name.len();
+ let mut upper_len = 0;
+ let mut meet_lower = false;
+ for i in self.pos..self.name.len() {
+ let c = self.name[i];
+ if b'A' <= c && c <= b'Z' {
+ if meet_lower {
+ // So it should be AaA or aaA
+ pos = i;
+ break;
+ }
+ upper_len += 1;
+ } else if c == b'_' {
+ pos = i;
+ break;
+ } else {
+ meet_lower = true;
+ if upper_len > 1 {
+ // So it should be AAa
+ pos = i - 1;
+ break;
+ }
+ }
+ }
+ let s = str::from_utf8(&self.name[self.pos..pos]).unwrap();
+ self.pos = pos;
+ Some(s)
+ }
+}
+
+/// Adjust method name to follow rust-guidelines.
+pub fn to_snake_case(name: &str) -> String {
+ let mut snake_method_name = String::with_capacity(name.len());
+ for s in NameSpliter::new(name) {
+ snake_method_name.push_str(&s.to_lowercase());
+ snake_method_name.push('_');
+ }
+ snake_method_name.pop();
+ snake_method_name
+}
+
+#[cfg(feature = "protobuf-codec")]
+pub fn to_camel_case(name: &str) -> String {
+ let mut camel_case_name = String::with_capacity(name.len());
+ for s in NameSpliter::new(name) {
+ let mut chs = s.chars();
+ camel_case_name.extend(chs.next().unwrap().to_uppercase());
+ camel_case_name.push_str(&s[1..].to_lowercase());
+ }
+ camel_case_name
+}
+
+pub fn fq_grpc(item: &str) -> String {
+ format!("::grpcio::{}", item)
+}
+
+pub enum MethodType {
+ Unary,
+ ClientStreaming,
+ ServerStreaming,
+ Duplex,
+}
+
+impl fmt::Display for MethodType {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ write!(
+ f,
+ "{}",
+ match self {
+ MethodType::Unary => "MethodType::Unary",
+ MethodType::ClientStreaming => "MethodType::ClientStreaming",
+ MethodType::ServerStreaming => "MethodType::ServerStreaming",
+ MethodType::Duplex => "MethodType::Duplex",
+ }
+ )
+ }
+}
+
+#[cfg(test)]
+mod test {
+ #[test]
+ fn test_snake_name() {
+ let cases = vec![
+ ("AsyncRequest", "async_request"),
+ ("asyncRequest", "async_request"),
+ ("async_request", "async_request"),
+ ("createID", "create_id"),
+ ("AsyncRClient", "async_r_client"),
+ ("CreateIDForReq", "create_id_for_req"),
+ ("Create_ID_For_Req", "create_id_for_req"),
+ ("Create_ID_For__Req", "create_id_for_req"),
+ ("ID", "id"),
+ ("id", "id"),
+ ];
+
+ for (origin, exp) in cases {
+ let res = super::to_snake_case(&origin);
+ assert_eq!(res, exp);
+ }
+ }
+
+ #[test]
+ #[cfg(feature = "protobuf-codec")]
+ fn test_camel_name() {
+ let cases = vec![
+ ("AsyncRequest", "AsyncRequest"),
+ ("asyncRequest", "AsyncRequest"),
+ ("async_request", "AsyncRequest"),
+ ("createID", "CreateId"),
+ ("AsyncRClient", "AsyncRClient"),
+ ("async_r_client", "AsyncRClient"),
+ ("CreateIDForReq", "CreateIdForReq"),
+ ("Create_ID_For_Req", "CreateIdForReq"),
+ ("Create_ID_For__Req", "CreateIdForReq"),
+ ("ID", "Id"),
+ ("id", "Id"),
+ ];
+
+ for (origin, exp) in cases {
+ let res = super::to_camel_case(&origin);
+ assert_eq!(res, exp);
+ }
+ }
+}