feat(build): Decouple codgen from prost (#170)
This commit is contained in:
+2
-2
@@ -103,7 +103,7 @@ name = "hyper-warp-server"
|
|||||||
path = "src/hyper_warp/server.rs"
|
path = "src/hyper_warp/server.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
tonic = { path = "../tonic", features = ["tls"] }
|
tonic = { path = "../tonic", features = ["tls", "data-prost"] }
|
||||||
prost = "0.6"
|
prost = "0.6"
|
||||||
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] }
|
||||||
futures = { version = "0.3", default-features = false, features = ["alloc"] }
|
futures = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||||
@@ -128,4 +128,4 @@ http-body = "0.3"
|
|||||||
pin-project = "0.4"
|
pin-project = "0.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { path = "../tonic-build" }
|
tonic-build = { path = "../tonic-build", features = ["prost"] }
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::compile_protos("proto/helloworld/helloworld.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/helloworld/helloworld.proto").unwrap();
|
||||||
tonic_build::compile_protos("proto/routeguide/route_guide.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/routeguide/route_guide.proto").unwrap();
|
||||||
tonic_build::compile_protos("proto/echo/echo.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/echo/echo.proto").unwrap();
|
||||||
tonic_build::compile_protos("proto/google/pubsub/pubsub.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/google/pubsub/pubsub.proto").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -129,7 +129,7 @@ At the root of your crate, create a `build.rs` file and add the following code:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tonic_build::compile_protos("proto/helloworld.proto")?;
|
tonic_build::prost::compile_protos("proto/helloworld.proto")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -199,7 +199,7 @@ Create a `build.rs` file at the root of your crate:
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::compile_protos("proto/route_guide.proto")
|
tonic_build::prost::compile_protos("proto/route_guide.proto")
|
||||||
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
.unwrap_or_else(|e| panic!("Failed to compile protos {:?}", e));
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -823,7 +823,7 @@ opposed to at build time, placing the resulting modules wherever we need them.
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::configure()
|
tonic_build::prost::configure()
|
||||||
.build_client(false)
|
.build_client(false)
|
||||||
.out_dir("another_crate/src/pb")
|
.out_dir("another_crate/src/pb")
|
||||||
.compile(&["path/my_proto.proto"], &["path"])
|
.compile(&["path/my_proto.proto"], &["path"])
|
||||||
|
|||||||
+1
-1
@@ -34,4 +34,4 @@ tracing-subscriber = "0.2.0-alpha"
|
|||||||
tracing-log = "0.1.0"
|
tracing-log = "0.1.0"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tonic-build = { path = "../tonic-build" }
|
tonic-build = { path = "../tonic-build", features=["prost"] }
|
||||||
|
|||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
let proto = "proto/grpc/testing/test.proto";
|
let proto = "proto/grpc/testing/test.proto";
|
||||||
|
|
||||||
tonic_build::compile_protos(proto).unwrap();
|
tonic_build::prost::compile_protos(proto).unwrap();
|
||||||
|
|
||||||
// prevent needing to rebuild if files (or deps) haven't changed
|
// prevent needing to rebuild if files (or deps) haven't changed
|
||||||
println!("cargo:rerun-if-changed={}", proto);
|
println!("cargo:rerun-if-changed={}", proto);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
fn main() -> Result<(), std::io::Error> {
|
fn main() -> Result<(), std::io::Error> {
|
||||||
tonic_build::configure()
|
tonic_build::prost::configure()
|
||||||
.build_server(false)
|
.build_server(false)
|
||||||
.build_client(true)
|
.build_client(true)
|
||||||
.extern_path(".uuid", "::uuid")
|
.extern_path(".uuid", "::uuid")
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::compile_protos("proto/includer.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/includer.proto").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::compile_protos("proto/foo.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/foo.proto").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
fn main() {
|
fn main() {
|
||||||
tonic_build::compile_protos("proto/wellknown.proto").unwrap();
|
tonic_build::prost::compile_protos("proto/wellknown.proto").unwrap();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,15 +16,16 @@ keywords = ["rpc", "grpc", "async", "codegen", "protobuf"]
|
|||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
prost-build = "0.6"
|
prost-build = { version = "0.6", optional = true }
|
||||||
syn = "1.0"
|
syn = "1.0"
|
||||||
quote = "1.0"
|
quote = "1.0"
|
||||||
proc-macro2 = "1.0"
|
proc-macro2 = "1.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["transport", "rustfmt"]
|
default = ["transport", "rustfmt", "prost"]
|
||||||
rustfmt = []
|
rustfmt = []
|
||||||
transport = []
|
transport = []
|
||||||
|
prost = ["prost-build"]
|
||||||
|
|
||||||
[package.metadata.docs.rs]
|
[package.metadata.docs.rs]
|
||||||
all-features = true
|
all-features = true
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ tonic-build = <tonic-version>
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tonic_build::compile_protos("proto/service.proto")?;
|
tonic_build::prost::compile_protos("proto/service.proto")?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -32,7 +32,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
```rust
|
```rust
|
||||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
tonic_build::configure()
|
tonic_build::prost::configure()
|
||||||
.build_server(false)
|
.build_server(false)
|
||||||
.compile(
|
.compile(
|
||||||
&["proto/helloworld/helloworld.proto"],
|
&["proto/helloworld/helloworld.proto"],
|
||||||
|
|||||||
+56
-31
@@ -1,15 +1,16 @@
|
|||||||
|
use super::schema::{Context, Method, Service};
|
||||||
use crate::{generate_doc_comments, naive_snake_case};
|
use crate::{generate_doc_comments, naive_snake_case};
|
||||||
use proc_macro2::TokenStream;
|
use proc_macro2::TokenStream;
|
||||||
use prost_build::{Method, Service};
|
|
||||||
use quote::{format_ident, quote};
|
use quote::{format_ident, quote};
|
||||||
|
|
||||||
pub(crate) fn generate(service: &Service, proto: &str) -> TokenStream {
|
/// Generate service for client
|
||||||
let service_ident = quote::format_ident!("{}Client", service.name);
|
pub fn generate<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream {
|
||||||
let client_mod = quote::format_ident!("{}_client", naive_snake_case(&service.name));
|
let service_ident = quote::format_ident!("{}Client", service.name());
|
||||||
let methods = generate_methods(service, proto);
|
let client_mod = quote::format_ident!("{}_client", naive_snake_case(&service.name()));
|
||||||
|
let methods = generate_methods(service, context);
|
||||||
|
|
||||||
let connect = generate_connect(&service_ident);
|
let connect = generate_connect(&service_ident);
|
||||||
let service_doc = generate_doc_comments(&service.comments.leading);
|
let service_doc = generate_doc_comments(service.comment());
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
/// Generated client implementations.
|
/// Generated client implementations.
|
||||||
@@ -75,22 +76,26 @@ fn generate_connect(_service_ident: &syn::Ident) -> TokenStream {
|
|||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_methods(service: &Service, proto: &str) -> TokenStream {
|
fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream {
|
||||||
let mut stream = TokenStream::new();
|
let mut stream = TokenStream::new();
|
||||||
|
|
||||||
for method in &service.methods {
|
for method in service.methods() {
|
||||||
|
use super::schema::Commentable;
|
||||||
|
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"/{}.{}/{}",
|
"/{}.{}/{}",
|
||||||
service.package, service.proto_name, method.proto_name
|
service.package(),
|
||||||
|
service.identifier(),
|
||||||
|
method.identifier()
|
||||||
);
|
);
|
||||||
|
|
||||||
stream.extend(generate_doc_comments(&method.comments.leading));
|
stream.extend(generate_doc_comments(method.comment()));
|
||||||
|
|
||||||
let method = match (method.client_streaming, method.server_streaming) {
|
let method = match (method.client_streaming(), method.server_streaming()) {
|
||||||
(false, false) => generate_unary(method, &proto, path),
|
(false, false) => generate_unary(method, &context, path),
|
||||||
(false, true) => generate_server_streaming(method, &proto, path),
|
(false, true) => generate_server_streaming(method, &context, path),
|
||||||
(true, false) => generate_client_streaming(method, &proto, path),
|
(true, false) => generate_client_streaming(method, &context, path),
|
||||||
(true, true) => generate_streaming(method, &proto, path),
|
(true, true) => generate_streaming(method, &context, path),
|
||||||
};
|
};
|
||||||
|
|
||||||
stream.extend(method);
|
stream.extend(method);
|
||||||
@@ -99,9 +104,14 @@ fn generate_methods(service: &Service, proto: &str) -> TokenStream {
|
|||||||
stream
|
stream
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_unary(method: &Method, proto: &str, path: String) -> TokenStream {
|
fn generate_unary<'a, T: Method<'a>>(
|
||||||
let ident = format_ident!("{}", method.name);
|
method: &T,
|
||||||
let (request, response) = crate::replace_wellknown(proto, &method);
|
context: &T::Context,
|
||||||
|
path: String,
|
||||||
|
) -> TokenStream {
|
||||||
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
let ident = format_ident!("{}", method.name());
|
||||||
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
pub async fn #ident(
|
pub async fn #ident(
|
||||||
@@ -111,17 +121,22 @@ fn generate_unary(method: &Method, proto: &str, path: String) -> TokenStream {
|
|||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
let path = http::uri::PathAndQuery::from_static(#path);
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
self.inner.unary(request.into_request(), path, codec).await
|
self.inner.unary(request.into_request(), path, codec).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_server_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
fn generate_server_streaming<'a, T: Method<'a>>(
|
||||||
let ident = format_ident!("{}", method.name);
|
method: &T,
|
||||||
|
context: &T::Context,
|
||||||
|
path: String,
|
||||||
|
) -> TokenStream {
|
||||||
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
let ident = format_ident!("{}", method.name());
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto, &method);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
pub async fn #ident(
|
pub async fn #ident(
|
||||||
@@ -131,17 +146,22 @@ fn generate_server_streaming(method: &Method, proto: &str, path: String) -> Toke
|
|||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
let path = http::uri::PathAndQuery::from_static(#path);
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
self.inner.server_streaming(request.into_request(), path, codec).await
|
self.inner.server_streaming(request.into_request(), path, codec).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_client_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
fn generate_client_streaming<'a, T: Method<'a>>(
|
||||||
let ident = format_ident!("{}", method.name);
|
method: &T,
|
||||||
|
context: &T::Context,
|
||||||
|
path: String,
|
||||||
|
) -> TokenStream {
|
||||||
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
let ident = format_ident!("{}", method.name());
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto, &method);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
pub async fn #ident(
|
pub async fn #ident(
|
||||||
@@ -151,17 +171,22 @@ fn generate_client_streaming(method: &Method, proto: &str, path: String) -> Toke
|
|||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
let path = http::uri::PathAndQuery::from_static(#path);
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
self.inner.client_streaming(request.into_streaming_request(), path, codec).await
|
self.inner.client_streaming(request.into_streaming_request(), path, codec).await
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
|
fn generate_streaming<'a, T: Method<'a>>(
|
||||||
let ident = format_ident!("{}", method.name);
|
method: &T,
|
||||||
|
context: &T::Context,
|
||||||
|
path: String,
|
||||||
|
) -> TokenStream {
|
||||||
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
let ident = format_ident!("{}", method.name());
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto, &method);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
pub async fn #ident(
|
pub async fn #ident(
|
||||||
@@ -171,7 +196,7 @@ fn generate_streaming(method: &Method, proto: &str, path: String) -> TokenStream
|
|||||||
self.inner.ready().await.map_err(|e| {
|
self.inner.ready().await.map_err(|e| {
|
||||||
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
|
||||||
})?;
|
})?;
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
let path = http::uri::PathAndQuery::from_static(#path);
|
let path = http::uri::PathAndQuery::from_static(#path);
|
||||||
self.inner.streaming(request.into_streaming_request(), path, codec).await
|
self.inner.streaming(request.into_streaming_request(), path, codec).await
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-241
@@ -23,7 +23,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! tonic_build::compile_protos("proto/service.proto")?;
|
//! tonic_build::prost::compile_protos("proto/service.proto")?;
|
||||||
//! Ok(())
|
//! Ok(())
|
||||||
//! }
|
//! }
|
||||||
//! ```
|
//! ```
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
//! fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
//! tonic_build::configure()
|
//! tonic_build::prost::configure()
|
||||||
//! .build_server(false)
|
//! .build_server(false)
|
||||||
//! .compile(
|
//! .compile(
|
||||||
//! &["proto/helloworld/helloworld.proto"],
|
//! &["proto/helloworld/helloworld.proto"],
|
||||||
@@ -57,163 +57,25 @@
|
|||||||
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
|
#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
|
||||||
|
|
||||||
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
|
use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
|
||||||
use prost_build::{Config, Method};
|
use quote::TokenStreamExt;
|
||||||
use quote::{ToTokens, TokenStreamExt};
|
|
||||||
|
/// Prost generator
|
||||||
|
#[cfg(feature = "prost")]
|
||||||
|
pub mod prost;
|
||||||
|
/// Traits to describe schema
|
||||||
|
pub mod schema;
|
||||||
|
|
||||||
#[cfg(feature = "rustfmt")]
|
#[cfg(feature = "rustfmt")]
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::{
|
|
||||||
io,
|
|
||||||
path::{Path, PathBuf},
|
|
||||||
};
|
|
||||||
|
|
||||||
mod client;
|
/// Serivce code generation for client
|
||||||
mod server;
|
pub mod client;
|
||||||
|
/// Serivce code generation for Server
|
||||||
/// Service generator builder.
|
pub mod server;
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Builder {
|
|
||||||
build_client: bool,
|
|
||||||
build_server: bool,
|
|
||||||
extern_path: Vec<(String, String)>,
|
|
||||||
field_attributes: Vec<(String, String)>,
|
|
||||||
type_attributes: Vec<(String, String)>,
|
|
||||||
out_dir: Option<PathBuf>,
|
|
||||||
#[cfg(feature = "rustfmt")]
|
|
||||||
format: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Builder {
|
|
||||||
/// Enable or disable gRPC client code generation.
|
|
||||||
pub fn build_client(mut self, enable: bool) -> Self {
|
|
||||||
self.build_client = enable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable or disable gRPC server code generation.
|
|
||||||
pub fn build_server(mut self, enable: bool) -> Self {
|
|
||||||
self.build_server = enable;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Enable the output to be formated by rustfmt.
|
|
||||||
#[cfg(feature = "rustfmt")]
|
|
||||||
pub fn format(mut self, run: bool) -> Self {
|
|
||||||
self.format = run;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set the output directory to generate code to.
|
|
||||||
///
|
|
||||||
/// Defaults to the `OUT_DIR` environment variable.
|
|
||||||
pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
|
|
||||||
self.out_dir = Some(out_dir.as_ref().to_path_buf());
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Declare externally provided Protobuf package or type.
|
|
||||||
///
|
|
||||||
/// Passed directly to `prost_build::Config.extern_path`.
|
|
||||||
/// Note that both the Protobuf path and the rust package paths should both be fully qualified.
|
|
||||||
/// i.e. Protobuf paths should start with "." and rust paths should start with "::"
|
|
||||||
pub fn extern_path(mut self, proto_path: impl AsRef<str>, rust_path: impl AsRef<str>) -> Self {
|
|
||||||
self.extern_path.push((
|
|
||||||
proto_path.as_ref().to_string(),
|
|
||||||
rust_path.as_ref().to_string(),
|
|
||||||
));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add additional attribute to matched messages, enums, and one-offs.
|
|
||||||
///
|
|
||||||
/// Passed directly to `prost_build::Config.field_attribute`.
|
|
||||||
pub fn field_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
|
|
||||||
self.field_attributes
|
|
||||||
.push((path.as_ref().to_string(), attribute.as_ref().to_string()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Add additional attribute to matched messages, enums, and one-offs.
|
|
||||||
///
|
|
||||||
/// Passed directly to `prost_build::Config.type_attribute`.
|
|
||||||
pub fn type_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
|
|
||||||
self.type_attributes
|
|
||||||
.push((path.as_ref().to_string(), attribute.as_ref().to_string()));
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compile the .proto files and execute code generation.
|
|
||||||
pub fn compile<P: AsRef<Path>>(self, protos: &[P], includes: &[P]) -> io::Result<()> {
|
|
||||||
let mut config = Config::new();
|
|
||||||
|
|
||||||
#[cfg(feature = "rustfmt")]
|
|
||||||
let format = self.format;
|
|
||||||
|
|
||||||
let out_dir = self
|
|
||||||
.out_dir
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| PathBuf::from(std::env::var("OUT_DIR").unwrap()));
|
|
||||||
|
|
||||||
config.out_dir(out_dir.clone());
|
|
||||||
for (proto_path, rust_path) in self.extern_path.iter() {
|
|
||||||
config.extern_path(proto_path, rust_path);
|
|
||||||
}
|
|
||||||
for (path, attr) in self.field_attributes.iter() {
|
|
||||||
config.field_attribute(path, attr);
|
|
||||||
}
|
|
||||||
for (path, attr) in self.type_attributes.iter() {
|
|
||||||
config.type_attribute(path, attr);
|
|
||||||
}
|
|
||||||
config.service_generator(Box::new(ServiceGenerator::new(self)));
|
|
||||||
|
|
||||||
config.compile_protos(protos, includes)?;
|
|
||||||
|
|
||||||
#[cfg(feature = "rustfmt")]
|
|
||||||
{
|
|
||||||
if format {
|
|
||||||
fmt(out_dir.to_str().expect("Expected utf8 out_dir"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Configure tonic-build code generation.
|
|
||||||
///
|
|
||||||
/// Use [`compile_protos`] instead if you don't need to tweak anything.
|
|
||||||
pub fn configure() -> Builder {
|
|
||||||
Builder {
|
|
||||||
build_client: true,
|
|
||||||
build_server: true,
|
|
||||||
out_dir: None,
|
|
||||||
extern_path: Vec::new(),
|
|
||||||
field_attributes: Vec::new(),
|
|
||||||
type_attributes: Vec::new(),
|
|
||||||
#[cfg(feature = "rustfmt")]
|
|
||||||
format: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Simple `.proto` compiling. Use [`configure`] instead if you need more options.
|
|
||||||
///
|
|
||||||
/// The include directory will be the parent folder of the specified path.
|
|
||||||
/// The package name will be the filename without the extension.
|
|
||||||
pub fn compile_protos(proto_path: impl AsRef<Path>) -> io::Result<()> {
|
|
||||||
let proto_path: &Path = proto_path.as_ref();
|
|
||||||
|
|
||||||
// directory the main .proto file resides in
|
|
||||||
let proto_dir = proto_path
|
|
||||||
.parent()
|
|
||||||
.expect("proto file should reside in a directory");
|
|
||||||
|
|
||||||
self::configure().compile(&[proto_path], &[proto_dir])?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Format files under the out_dir with rustfmt
|
||||||
#[cfg(feature = "rustfmt")]
|
#[cfg(feature = "rustfmt")]
|
||||||
fn fmt(out_dir: &str) {
|
pub fn fmt(out_dir: &str) {
|
||||||
let dir = std::fs::read_dir(out_dir).unwrap();
|
let dir = std::fs::read_dir(out_dir).unwrap();
|
||||||
|
|
||||||
for entry in dir {
|
for entry in dir {
|
||||||
@@ -232,73 +94,13 @@ fn fmt(out_dir: &str) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ServiceGenerator {
|
|
||||||
builder: Builder,
|
|
||||||
clients: TokenStream,
|
|
||||||
servers: TokenStream,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ServiceGenerator {
|
|
||||||
fn new(builder: Builder) -> Self {
|
|
||||||
ServiceGenerator {
|
|
||||||
builder,
|
|
||||||
clients: TokenStream::default(),
|
|
||||||
servers: TokenStream::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl prost_build::ServiceGenerator for ServiceGenerator {
|
|
||||||
fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
|
|
||||||
let path = "super";
|
|
||||||
|
|
||||||
if self.builder.build_server {
|
|
||||||
let server = server::generate(&service, path);
|
|
||||||
self.servers.extend(server);
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.builder.build_client {
|
|
||||||
let client = client::generate(&service, path);
|
|
||||||
self.clients.extend(client);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn finalize(&mut self, buf: &mut String) {
|
|
||||||
if self.builder.build_client && !self.clients.is_empty() {
|
|
||||||
let clients = &self.clients;
|
|
||||||
|
|
||||||
let client_service = quote::quote! {
|
|
||||||
#clients
|
|
||||||
};
|
|
||||||
|
|
||||||
let code = format!("{}", client_service);
|
|
||||||
buf.push_str(&code);
|
|
||||||
|
|
||||||
self.clients = TokenStream::default();
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.builder.build_server && !self.servers.is_empty() {
|
|
||||||
let servers = &self.servers;
|
|
||||||
|
|
||||||
let server_service = quote::quote! {
|
|
||||||
#servers
|
|
||||||
};
|
|
||||||
|
|
||||||
let code = format!("{}", server_service);
|
|
||||||
buf.push_str(&code);
|
|
||||||
|
|
||||||
self.servers = TokenStream::default();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate a singular line of a doc comment
|
// Generate a singular line of a doc comment
|
||||||
fn generate_doc_comment(comment: &str) -> TokenStream {
|
fn generate_doc_comment<S: AsRef<str>>(comment: S) -> TokenStream {
|
||||||
let mut doc_stream = TokenStream::new();
|
let mut doc_stream = TokenStream::new();
|
||||||
|
|
||||||
doc_stream.append(Ident::new("doc", Span::call_site()));
|
doc_stream.append(Ident::new("doc", Span::call_site()));
|
||||||
doc_stream.append(Punct::new('=', Spacing::Alone));
|
doc_stream.append(Punct::new('=', Spacing::Alone));
|
||||||
doc_stream.append(Literal::string(&comment));
|
doc_stream.append(Literal::string(comment.as_ref()));
|
||||||
|
|
||||||
let group = Group::new(Delimiter::Bracket, doc_stream);
|
let group = Group::new(Delimiter::Bracket, doc_stream);
|
||||||
|
|
||||||
@@ -309,40 +111,18 @@ fn generate_doc_comment(comment: &str) -> TokenStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Generate a larger doc comment composed of many lines of doc comments
|
// Generate a larger doc comment composed of many lines of doc comments
|
||||||
fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
|
fn generate_doc_comments<'a, T: AsRef<str> + 'a, C: IntoIterator<Item = &'a T>>(
|
||||||
|
comments: C,
|
||||||
|
) -> TokenStream {
|
||||||
let mut stream = TokenStream::new();
|
let mut stream = TokenStream::new();
|
||||||
|
|
||||||
for comment in comments {
|
for comment in comments {
|
||||||
stream.extend(generate_doc_comment(comment.as_ref()));
|
stream.extend(generate_doc_comment(comment));
|
||||||
}
|
}
|
||||||
|
|
||||||
stream
|
stream
|
||||||
}
|
}
|
||||||
|
|
||||||
fn replace_wellknown(proto_path: &str, method: &Method) -> (TokenStream, TokenStream) {
|
|
||||||
let request = if method.input_proto_type.starts_with(".google.protobuf")
|
|
||||||
|| method.input_type.starts_with("::")
|
|
||||||
{
|
|
||||||
method.input_type.parse::<TokenStream>().unwrap()
|
|
||||||
} else {
|
|
||||||
syn::parse_str::<syn::Path>(&format!("{}::{}", proto_path, method.input_type))
|
|
||||||
.unwrap()
|
|
||||||
.to_token_stream()
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = if method.output_proto_type.starts_with(".google.protobuf")
|
|
||||||
|| method.output_type.starts_with("::")
|
|
||||||
{
|
|
||||||
method.output_type.parse::<TokenStream>().unwrap()
|
|
||||||
} else {
|
|
||||||
syn::parse_str::<syn::Path>(&format!("{}::{}", proto_path, method.output_type))
|
|
||||||
.unwrap()
|
|
||||||
.to_token_stream()
|
|
||||||
};
|
|
||||||
|
|
||||||
(request, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn naive_snake_case(name: &str) -> String {
|
fn naive_snake_case(name: &str) -> String {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
let mut it = name.chars().peekable();
|
let mut it = name.chars().peekable();
|
||||||
|
|||||||
@@ -0,0 +1,320 @@
|
|||||||
|
use super::{client, schema, server};
|
||||||
|
use proc_macro2::TokenStream;
|
||||||
|
use prost_build::{Config, Method, Service};
|
||||||
|
use quote::ToTokens;
|
||||||
|
use std::io;
|
||||||
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
impl<'a> schema::Commentable<'a> for Service {
|
||||||
|
type Comment = String;
|
||||||
|
type CommentContainer = &'a Vec<Self::Comment>;
|
||||||
|
|
||||||
|
fn comment(&'a self) -> Self::CommentContainer {
|
||||||
|
&self.comments.leading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Context data used while generate prost service
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ProstContext {
|
||||||
|
/// relative path to proto definitions from service definitions
|
||||||
|
pub proto_path: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl schema::Context for ProstContext {
|
||||||
|
fn codec_name(&self) -> &str {
|
||||||
|
"tonic::codec::ProstCodec"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> schema::Service<'a> for Service {
|
||||||
|
type Method = Method;
|
||||||
|
type MethodContainer = &'a Vec<Self::Method>;
|
||||||
|
type Context = ProstContext;
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn package(&self) -> &str {
|
||||||
|
&self.package
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identifier(&self) -> &str {
|
||||||
|
&self.proto_name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn methods(&'a self) -> Self::MethodContainer {
|
||||||
|
&self.methods
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> schema::Commentable<'a> for Method {
|
||||||
|
type Comment = String;
|
||||||
|
type CommentContainer = &'a Vec<Self::Comment>;
|
||||||
|
|
||||||
|
fn comment(&'a self) -> Self::CommentContainer {
|
||||||
|
&self.comments.leading
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> schema::Method<'a> for Method {
|
||||||
|
type Context = ProstContext;
|
||||||
|
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
&self.name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn identifier(&self) -> &str {
|
||||||
|
&self.proto_name
|
||||||
|
}
|
||||||
|
|
||||||
|
fn client_streaming(&self) -> bool {
|
||||||
|
self.client_streaming
|
||||||
|
}
|
||||||
|
|
||||||
|
fn server_streaming(&self) -> bool {
|
||||||
|
self.server_streaming
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_response_name(&self, context: &Self::Context) -> (TokenStream, TokenStream) {
|
||||||
|
let request = if self.input_proto_type.starts_with(".google.protobuf")
|
||||||
|
|| self.input_type.starts_with("::")
|
||||||
|
{
|
||||||
|
self.input_type.parse::<TokenStream>().unwrap()
|
||||||
|
} else {
|
||||||
|
syn::parse_str::<syn::Path>(&format!("{}::{}", context.proto_path, self.input_type))
|
||||||
|
.unwrap()
|
||||||
|
.to_token_stream()
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = if self.output_proto_type.starts_with(".google.protobuf")
|
||||||
|
|| self.output_type.starts_with("::")
|
||||||
|
{
|
||||||
|
self.output_type.parse::<TokenStream>().unwrap()
|
||||||
|
} else {
|
||||||
|
syn::parse_str::<syn::Path>(&format!("{}::{}", context.proto_path, self.output_type))
|
||||||
|
.unwrap()
|
||||||
|
.to_token_stream()
|
||||||
|
};
|
||||||
|
|
||||||
|
(request, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn compile<P: AsRef<Path>>(
|
||||||
|
builder: Builder,
|
||||||
|
out_dir: PathBuf,
|
||||||
|
protos: &[P],
|
||||||
|
includes: &[P],
|
||||||
|
) -> std::io::Result<()> {
|
||||||
|
let mut config = Config::new();
|
||||||
|
|
||||||
|
config.out_dir(out_dir);
|
||||||
|
for (proto_path, rust_path) in builder.extern_path.iter() {
|
||||||
|
config.extern_path(proto_path, rust_path);
|
||||||
|
}
|
||||||
|
for (prost_path, attr) in builder.field_attributes.iter() {
|
||||||
|
config.field_attribute(prost_path, attr);
|
||||||
|
}
|
||||||
|
for (prost_path, attr) in builder.type_attributes.iter() {
|
||||||
|
config.type_attribute(prost_path, attr);
|
||||||
|
}
|
||||||
|
config.service_generator(Box::new(ServiceGenerator::new(builder)));
|
||||||
|
|
||||||
|
config.compile_protos(protos, includes)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ServiceGenerator {
|
||||||
|
builder: Builder,
|
||||||
|
clients: TokenStream,
|
||||||
|
servers: TokenStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ServiceGenerator {
|
||||||
|
fn new(builder: Builder) -> Self {
|
||||||
|
ServiceGenerator {
|
||||||
|
builder,
|
||||||
|
clients: TokenStream::default(),
|
||||||
|
servers: TokenStream::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl prost_build::ServiceGenerator for ServiceGenerator {
|
||||||
|
fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
|
||||||
|
let context = ProstContext {
|
||||||
|
proto_path: String::from("super"),
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.builder.build_server {
|
||||||
|
let server = server::generate(&service, &context);
|
||||||
|
self.servers.extend(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.builder.build_client {
|
||||||
|
let client = client::generate(&service, &context);
|
||||||
|
self.clients.extend(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn finalize(&mut self, buf: &mut String) {
|
||||||
|
if self.builder.build_client && !self.clients.is_empty() {
|
||||||
|
let clients = &self.clients;
|
||||||
|
|
||||||
|
let client_service = quote::quote! {
|
||||||
|
#clients
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = format!("{}", client_service);
|
||||||
|
buf.push_str(&code);
|
||||||
|
|
||||||
|
self.clients = TokenStream::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.builder.build_server && !self.servers.is_empty() {
|
||||||
|
let servers = &self.servers;
|
||||||
|
|
||||||
|
let server_service = quote::quote! {
|
||||||
|
#servers
|
||||||
|
};
|
||||||
|
|
||||||
|
let code = format!("{}", server_service);
|
||||||
|
buf.push_str(&code);
|
||||||
|
|
||||||
|
self.servers = TokenStream::default();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service generator builder.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Builder {
|
||||||
|
pub(crate) build_client: bool,
|
||||||
|
pub(crate) build_server: bool,
|
||||||
|
pub(crate) extern_path: Vec<(String, String)>,
|
||||||
|
pub(crate) field_attributes: Vec<(String, String)>,
|
||||||
|
pub(crate) type_attributes: Vec<(String, String)>,
|
||||||
|
|
||||||
|
out_dir: Option<PathBuf>,
|
||||||
|
#[cfg(feature = "rustfmt")]
|
||||||
|
format: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
/// Enable or disable gRPC client code generation.
|
||||||
|
pub fn build_client(mut self, enable: bool) -> Self {
|
||||||
|
self.build_client = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable or disable gRPC server code generation.
|
||||||
|
pub fn build_server(mut self, enable: bool) -> Self {
|
||||||
|
self.build_server = enable;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Enable the output to be formated by rustfmt.
|
||||||
|
#[cfg(feature = "rustfmt")]
|
||||||
|
pub fn format(mut self, run: bool) -> Self {
|
||||||
|
self.format = run;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the output directory to generate code to.
|
||||||
|
///
|
||||||
|
/// Defaults to the `OUT_DIR` environment variable.
|
||||||
|
pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
|
||||||
|
self.out_dir = Some(out_dir.as_ref().to_path_buf());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Declare externally provided Protobuf package or type.
|
||||||
|
///
|
||||||
|
/// Passed directly to `prost_build::Config.extern_path`.
|
||||||
|
/// Note that both the Protobuf path and the rust package paths should both be fully qualified.
|
||||||
|
/// i.e. Protobuf paths should start with "." and rust paths should start with "::"
|
||||||
|
pub fn extern_path(mut self, proto_path: impl AsRef<str>, rust_path: impl AsRef<str>) -> Self {
|
||||||
|
self.extern_path.push((
|
||||||
|
proto_path.as_ref().to_string(),
|
||||||
|
rust_path.as_ref().to_string(),
|
||||||
|
));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add additional attribute to matched messages, enums, and one-offs.
|
||||||
|
///
|
||||||
|
/// Passed directly to `prost_build::Config.field_attribute`.
|
||||||
|
pub fn field_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
|
||||||
|
self.field_attributes
|
||||||
|
.push((path.as_ref().to_string(), attribute.as_ref().to_string()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add additional attribute to matched messages, enums, and one-offs.
|
||||||
|
///
|
||||||
|
/// Passed directly to `prost_build::Config.type_attribute`.
|
||||||
|
pub fn type_attribute<P: AsRef<str>, A: AsRef<str>>(mut self, path: P, attribute: A) -> Self {
|
||||||
|
self.type_attributes
|
||||||
|
.push((path.as_ref().to_string(), attribute.as_ref().to_string()));
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile the .proto files and execute code generation.
|
||||||
|
pub fn compile<P: AsRef<Path>>(self, protos: &[P], includes: &[P]) -> io::Result<()> {
|
||||||
|
let out_dir = if let Some(out_dir) = self.out_dir.as_ref() {
|
||||||
|
out_dir.clone()
|
||||||
|
} else {
|
||||||
|
PathBuf::from(std::env::var("OUT_DIR").unwrap())
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "rustfmt")]
|
||||||
|
let format = self.format;
|
||||||
|
|
||||||
|
compile(self, out_dir.clone(), protos, includes)?;
|
||||||
|
|
||||||
|
#[cfg(feature = "rustfmt")]
|
||||||
|
{
|
||||||
|
if format {
|
||||||
|
super::fmt(out_dir.to_str().expect("Expected utf8 out_dir"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure tonic-build code generation.
|
||||||
|
///
|
||||||
|
/// Use [`compile_protos`] instead if you don't need to tweak anything.
|
||||||
|
pub fn configure() -> Builder {
|
||||||
|
Builder {
|
||||||
|
build_client: true,
|
||||||
|
build_server: true,
|
||||||
|
out_dir: None,
|
||||||
|
extern_path: Vec::new(),
|
||||||
|
field_attributes: Vec::new(),
|
||||||
|
type_attributes: Vec::new(),
|
||||||
|
#[cfg(feature = "rustfmt")]
|
||||||
|
format: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Simple `.proto` compiling. Use [`configure`] instead if you need more options.
|
||||||
|
///
|
||||||
|
/// The include directory will be the parent folder of the specified path.
|
||||||
|
/// The package name will be the filename without the extension.
|
||||||
|
pub fn compile_protos(proto_path: impl AsRef<Path>) -> io::Result<()> {
|
||||||
|
let proto_path: &Path = proto_path.as_ref();
|
||||||
|
|
||||||
|
// directory the main .proto file resides in
|
||||||
|
let proto_dir = proto_path
|
||||||
|
.parent()
|
||||||
|
.expect("proto file should reside in a directory");
|
||||||
|
|
||||||
|
self::configure().compile(&[proto_path], &[proto_dir])?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
use proc_macro2::TokenStream;
|
||||||
|
|
||||||
|
/// Context data used in code generation
|
||||||
|
pub trait Context {
|
||||||
|
/// Provide name of tonic compatibale codec
|
||||||
|
fn codec_name(&self) -> &str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Item has comment
|
||||||
|
pub trait Commentable<'a> {
|
||||||
|
/// Comment type
|
||||||
|
type Comment: AsRef<str> + 'a;
|
||||||
|
/// Container has comments.
|
||||||
|
type CommentContainer: IntoIterator<Item = &'a Self::Comment>;
|
||||||
|
/// Get comments about this item
|
||||||
|
fn comment(&'a self) -> Self::CommentContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Service
|
||||||
|
pub trait Service<'a>: Commentable<'a> {
|
||||||
|
/// Method type
|
||||||
|
type Method: Method<'a, Context = Self::Context> + 'a;
|
||||||
|
/// Container has methods
|
||||||
|
type MethodContainer: IntoIterator<Item = &'a Self::Method>;
|
||||||
|
/// Common context
|
||||||
|
type Context: Context + 'a;
|
||||||
|
|
||||||
|
/// Name of service
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
/// Package name of service
|
||||||
|
fn package(&self) -> &str;
|
||||||
|
/// Identifier used to generate type name
|
||||||
|
fn identifier(&self) -> &str;
|
||||||
|
/// Methods provided by service
|
||||||
|
fn methods(&'a self) -> Self::MethodContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Method
|
||||||
|
pub trait Method<'a>: Commentable<'a> {
|
||||||
|
/// Common context
|
||||||
|
type Context: Context + 'a;
|
||||||
|
|
||||||
|
/// Name of method
|
||||||
|
fn name(&self) -> &str;
|
||||||
|
/// Identifier used to generate type name
|
||||||
|
fn identifier(&self) -> &str;
|
||||||
|
/// Method is streamed by client
|
||||||
|
fn client_streaming(&self) -> bool;
|
||||||
|
/// Method is streamed by server
|
||||||
|
fn server_streaming(&self) -> bool;
|
||||||
|
/// Type name of request and response
|
||||||
|
fn request_response_name(&self, context: &Self::Context) -> (TokenStream, TokenStream);
|
||||||
|
}
|
||||||
+72
-58
@@ -1,20 +1,21 @@
|
|||||||
|
use super::schema::{Commentable, Context, Method, Service};
|
||||||
use crate::{generate_doc_comment, generate_doc_comments, naive_snake_case};
|
use crate::{generate_doc_comment, generate_doc_comments, naive_snake_case};
|
||||||
use proc_macro2::{Span, TokenStream};
|
use proc_macro2::{Span, TokenStream};
|
||||||
use prost_build::{Method, Service};
|
|
||||||
use quote::quote;
|
use quote::quote;
|
||||||
use syn::{Ident, Lit, LitStr};
|
use syn::{Ident, Lit, LitStr};
|
||||||
|
|
||||||
pub(crate) fn generate(service: &Service, proto_path: &str) -> TokenStream {
|
/// Generate service for Server
|
||||||
let methods = generate_methods(&service, proto_path);
|
pub fn generate<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream {
|
||||||
|
let methods = generate_methods(service, context);
|
||||||
|
|
||||||
let server_service = quote::format_ident!("{}Server", service.name);
|
let server_service = quote::format_ident!("{}Server", service.name());
|
||||||
let server_trait = quote::format_ident!("{}", service.name);
|
let server_trait = quote::format_ident!("{}", service.name());
|
||||||
let server_mod = quote::format_ident!("{}_server", naive_snake_case(&service.name));
|
let server_mod = quote::format_ident!("{}_server", naive_snake_case(&service.name()));
|
||||||
let generated_trait = generate_trait(service, proto_path, server_trait.clone());
|
let generated_trait = generate_trait(service, context, server_trait.clone());
|
||||||
let service_doc = generate_doc_comments(&service.comments.leading);
|
let service_doc = generate_doc_comments(service.comment());
|
||||||
|
|
||||||
// Transport based implementations
|
// Transport based implementations
|
||||||
let path = format!("{}.{}", service.package, service.proto_name);
|
let path = format!("{}.{}", service.package(), service.identifier());
|
||||||
let transport = generate_transport(&server_service, &server_trait, &path);
|
let transport = generate_transport(&server_service, &server_trait, &path);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -98,11 +99,15 @@ pub(crate) fn generate(service: &Service, proto_path: &str) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_trait(service: &Service, proto_path: &str, server_trait: Ident) -> TokenStream {
|
fn generate_trait<'a, T: Service<'a>>(
|
||||||
let methods = generate_trait_methods(service, proto_path);
|
service: &'a T,
|
||||||
|
context: &T::Context,
|
||||||
|
server_trait: Ident,
|
||||||
|
) -> TokenStream {
|
||||||
|
let methods = generate_trait_methods(service, context);
|
||||||
let trait_doc = generate_doc_comment(&format!(
|
let trait_doc = generate_doc_comment(&format!(
|
||||||
"Generated trait containing gRPC methods that should be implemented for use with {}Server.",
|
"Generated trait containing gRPC methods that should be implemented for use with {}Server.",
|
||||||
service.name
|
service.name()
|
||||||
));
|
));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -114,17 +119,17 @@ fn generate_trait(service: &Service, proto_path: &str, server_trait: Ident) -> T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_trait_methods(service: &Service, proto_path: &str) -> TokenStream {
|
fn generate_trait_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream {
|
||||||
let mut stream = TokenStream::new();
|
let mut stream = TokenStream::new();
|
||||||
|
|
||||||
for method in &service.methods {
|
for method in service.methods() {
|
||||||
let name = quote::format_ident!("{}", method.name);
|
let name = quote::format_ident!("{}", method.name());
|
||||||
|
|
||||||
let (req_message, res_message) = crate::replace_wellknown(proto_path, &method);
|
let (req_message, res_message) = method.request_response_name(context);
|
||||||
|
|
||||||
let method_doc = generate_doc_comments(&method.comments.leading);
|
let method_doc = generate_doc_comments(method.comment());
|
||||||
|
|
||||||
let method = match (method.client_streaming, method.server_streaming) {
|
let method = match (method.client_streaming(), method.server_streaming()) {
|
||||||
(false, false) => {
|
(false, false) => {
|
||||||
quote! {
|
quote! {
|
||||||
#method_doc
|
#method_doc
|
||||||
@@ -140,10 +145,10 @@ fn generate_trait_methods(service: &Service, proto_path: &str) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
let stream = quote::format_ident!("{}Stream", method.proto_name);
|
let stream = quote::format_ident!("{}Stream", method.identifier());
|
||||||
let stream_doc = generate_doc_comment(&format!(
|
let stream_doc = generate_doc_comment(&format!(
|
||||||
"Server streaming response type for the {} method.",
|
"Server streaming response type for the {} method.",
|
||||||
method.proto_name
|
method.identifier()
|
||||||
));
|
));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -156,10 +161,10 @@ fn generate_trait_methods(service: &Service, proto_path: &str) -> TokenStream {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
(true, true) => {
|
(true, true) => {
|
||||||
let stream = quote::format_ident!("{}Stream", method.proto_name);
|
let stream = quote::format_ident!("{}Stream", method.identifier());
|
||||||
let stream_doc = generate_doc_comment(&format!(
|
let stream_doc = generate_doc_comment(&format!(
|
||||||
"Server streaming response type for the {} method.",
|
"Server streaming response type for the {} method.",
|
||||||
method.proto_name
|
method.identifier()
|
||||||
));
|
));
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -203,29 +208,31 @@ fn generate_transport(
|
|||||||
TokenStream::new()
|
TokenStream::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_methods(service: &Service, proto_path: &str) -> TokenStream {
|
fn generate_methods<'a, T: Service<'a>>(service: &'a T, context: &T::Context) -> TokenStream {
|
||||||
let mut stream = TokenStream::new();
|
let mut stream = TokenStream::new();
|
||||||
|
|
||||||
for method in &service.methods {
|
for method in service.methods() {
|
||||||
let path = format!(
|
let path = format!(
|
||||||
"/{}.{}/{}",
|
"/{}.{}/{}",
|
||||||
service.package, service.proto_name, method.proto_name
|
service.package(),
|
||||||
|
service.identifier(),
|
||||||
|
method.identifier()
|
||||||
);
|
);
|
||||||
let method_path = Lit::Str(LitStr::new(&path, Span::call_site()));
|
let method_path = Lit::Str(LitStr::new(&path, Span::call_site()));
|
||||||
let ident = quote::format_ident!("{}", method.name);
|
let ident = quote::format_ident!("{}", method.name());
|
||||||
let server_trait = quote::format_ident!("{}", service.name);
|
let server_trait = quote::format_ident!("{}", service.name());
|
||||||
|
|
||||||
let method_stream = match (method.client_streaming, method.server_streaming) {
|
let method_stream = match (method.client_streaming(), method.server_streaming()) {
|
||||||
(false, false) => generate_unary(method, ident, proto_path, server_trait),
|
(false, false) => generate_unary(method, ident, context, server_trait),
|
||||||
|
|
||||||
(false, true) => {
|
(false, true) => {
|
||||||
generate_server_streaming(method, ident.clone(), proto_path, server_trait)
|
generate_server_streaming(method, ident.clone(), context, server_trait)
|
||||||
}
|
}
|
||||||
(true, false) => {
|
(true, false) => {
|
||||||
generate_client_streaming(method, ident.clone(), proto_path, server_trait)
|
generate_client_streaming(method, ident.clone(), context, server_trait)
|
||||||
}
|
}
|
||||||
|
|
||||||
(true, true) => generate_streaming(method, ident.clone(), proto_path, server_trait),
|
(true, true) => generate_streaming(method, ident.clone(), context, server_trait),
|
||||||
};
|
};
|
||||||
|
|
||||||
let method = quote! {
|
let method = quote! {
|
||||||
@@ -239,15 +246,17 @@ fn generate_methods(service: &Service, proto_path: &str) -> TokenStream {
|
|||||||
stream
|
stream
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_unary(
|
fn generate_unary<'a, T: Method<'a>>(
|
||||||
method: &Method,
|
method: &T,
|
||||||
method_ident: Ident,
|
method_ident: Ident,
|
||||||
proto_path: &str,
|
context: &T::Context,
|
||||||
server_trait: Ident,
|
server_trait: Ident,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let service_ident = quote::format_ident!("{}Svc", method.proto_name);
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto_path, &method);
|
let service_ident = quote::format_ident!("{}Svc", method.identifier());
|
||||||
|
|
||||||
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
||||||
@@ -270,7 +279,7 @@ fn generate_unary(
|
|||||||
let interceptor = inner.1.clone();
|
let interceptor = inner.1.clone();
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = if let Some(interceptor) = interceptor {
|
let mut grpc = if let Some(interceptor) = interceptor {
|
||||||
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
||||||
@@ -286,17 +295,19 @@ fn generate_unary(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_server_streaming(
|
fn generate_server_streaming<'a, T: Method<'a>>(
|
||||||
method: &Method,
|
method: &T,
|
||||||
method_ident: Ident,
|
method_ident: Ident,
|
||||||
proto_path: &str,
|
context: &T::Context,
|
||||||
server_trait: Ident,
|
server_trait: Ident,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let service_ident = quote::format_ident!("{}Svc", method.proto_name);
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto_path, &method);
|
let service_ident = quote::format_ident!("{}Svc", method.identifier());
|
||||||
|
|
||||||
let response_stream = quote::format_ident!("{}Stream", method.proto_name);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
|
let response_stream = quote::format_ident!("{}Stream", method.identifier());
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
||||||
@@ -321,7 +332,7 @@ fn generate_server_streaming(
|
|||||||
let interceptor = inner.1;
|
let interceptor = inner.1;
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = if let Some(interceptor) = interceptor {
|
let mut grpc = if let Some(interceptor) = interceptor {
|
||||||
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
||||||
@@ -337,15 +348,16 @@ fn generate_server_streaming(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_client_streaming(
|
fn generate_client_streaming<'a, T: Method<'a>>(
|
||||||
method: &Method,
|
method: &T,
|
||||||
method_ident: Ident,
|
method_ident: Ident,
|
||||||
proto_path: &str,
|
context: &T::Context,
|
||||||
server_trait: Ident,
|
server_trait: Ident,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let service_ident = quote::format_ident!("{}Svc", method.proto_name);
|
let service_ident = quote::format_ident!("{}Svc", method.identifier());
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto_path, &method);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
struct #service_ident<T: #server_trait >(pub Arc<T>);
|
||||||
@@ -370,7 +382,7 @@ fn generate_client_streaming(
|
|||||||
let interceptor = inner.1;
|
let interceptor = inner.1;
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = if let Some(interceptor) = interceptor {
|
let mut grpc = if let Some(interceptor) = interceptor {
|
||||||
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
||||||
@@ -386,17 +398,19 @@ fn generate_client_streaming(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn generate_streaming(
|
fn generate_streaming<'a, T: Method<'a>>(
|
||||||
method: &Method,
|
method: &T,
|
||||||
method_ident: Ident,
|
method_ident: Ident,
|
||||||
proto_path: &str,
|
context: &T::Context,
|
||||||
server_trait: Ident,
|
server_trait: Ident,
|
||||||
) -> TokenStream {
|
) -> TokenStream {
|
||||||
let service_ident = quote::format_ident!("{}Svc", method.proto_name);
|
let codec_name = syn::parse_str::<syn::Path>(context.codec_name()).unwrap();
|
||||||
|
|
||||||
let (request, response) = crate::replace_wellknown(proto_path, &method);
|
let service_ident = quote::format_ident!("{}Svc", method.identifier());
|
||||||
|
|
||||||
let response_stream = quote::format_ident!("{}Stream", method.proto_name);
|
let (request, response) = method.request_response_name(context);
|
||||||
|
|
||||||
|
let response_stream = quote::format_ident!("{}Stream", method.identifier());
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
struct #service_ident<T: #server_trait>(pub Arc<T>);
|
struct #service_ident<T: #server_trait>(pub Arc<T>);
|
||||||
@@ -421,7 +435,7 @@ fn generate_streaming(
|
|||||||
let interceptor = inner.1;
|
let interceptor = inner.1;
|
||||||
let inner = inner.0;
|
let inner = inner.0;
|
||||||
let method = #service_ident(inner);
|
let method = #service_ident(inner);
|
||||||
let codec = tonic::codec::ProstCodec::default();
|
let codec = #codec_name::default();
|
||||||
|
|
||||||
let mut grpc = if let Some(interceptor) = interceptor {
|
let mut grpc = if let Some(interceptor) = interceptor {
|
||||||
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
tonic::server::Grpc::with_interceptor(codec, interceptor)
|
||||||
|
|||||||
+3
-2
@@ -23,8 +23,8 @@ categories = ["web-programming", "network-programming", "asynchronous"]
|
|||||||
keywords = ["rpc", "grpc", "async", "futures", "protobuf"]
|
keywords = ["rpc", "grpc", "async", "futures", "protobuf"]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["transport", "codegen"]
|
default = ["transport", "codegen", "data-prost"]
|
||||||
codegen = ["async-trait", "prost", "prost-derive"]
|
codegen = ["async-trait"]
|
||||||
transport = [
|
transport = [
|
||||||
"hyper",
|
"hyper",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -35,6 +35,7 @@ transport = [
|
|||||||
]
|
]
|
||||||
tls = ["transport", "tokio-rustls"]
|
tls = ["transport", "tokio-rustls"]
|
||||||
tls-roots = ["tls", "rustls-native-certs"]
|
tls-roots = ["tls", "rustls-native-certs"]
|
||||||
|
data-prost = ["prost", "prost-derive"]
|
||||||
|
|
||||||
# [[bench]]
|
# [[bench]]
|
||||||
# name = "bench_main"
|
# name = "bench_main"
|
||||||
|
|||||||
@@ -6,18 +6,18 @@
|
|||||||
mod buffer;
|
mod buffer;
|
||||||
mod decode;
|
mod decode;
|
||||||
mod encode;
|
mod encode;
|
||||||
#[cfg(feature = "prost")]
|
#[cfg(feature = "data-prost")]
|
||||||
mod prost;
|
mod prost;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(all(test, feature = "data-prost"))]
|
||||||
mod tests;
|
mod prost_tests;
|
||||||
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
pub use self::decode::Streaming;
|
pub use self::decode::Streaming;
|
||||||
pub(crate) use self::encode::{encode_client, encode_server};
|
pub(crate) use self::encode::{encode_client, encode_server};
|
||||||
#[cfg(feature = "prost")]
|
#[cfg(feature = "data-prost")]
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
|
#[cfg_attr(docsrs, doc(cfg(feature = "data-prost")))]
|
||||||
pub use self::prost::ProstCodec;
|
pub use self::prost::ProstCodec;
|
||||||
use crate::Status;
|
use crate::Status;
|
||||||
pub use buffer::{DecodeBuf, EncodeBuf};
|
pub use buffer::{DecodeBuf, EncodeBuf};
|
||||||
|
|||||||
Reference in New Issue
Block a user