diff options
author | Ivan Lozano <ivanlozano@google.com> | 2020-11-05 19:23:18 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-11-05 19:23:18 +0000 |
commit | c70e5c7848cd7903c054b6e786d505295e8305c0 (patch) | |
tree | e327cafa4b052576b6094ec8f789657193c83767 | |
parent | e176dc85c8c71670937948ec0ef1955a61a05379 (diff) | |
parent | 7d2f75e2f6df3106073ac1c4a8646699d59bdaf1 (diff) | |
download | grpcio-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.json | 5 | ||||
-rw-r--r-- | Cargo.lock | 340 | ||||
-rw-r--r-- | Cargo.toml | 56 | ||||
-rw-r--r-- | Cargo.toml.orig | 29 | ||||
-rw-r--r-- | src/bin/grpc_rust_plugin.rs | 30 | ||||
-rw-r--r-- | src/codegen.rs | 708 | ||||
-rw-r--r-- | src/lib.rs | 8 | ||||
-rw-r--r-- | src/prost_codegen.rs | 538 | ||||
-rw-r--r-- | src/util.rs | 155 |
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); + } + } +} |