From 3cba9f5f6f638bb8a3a57390d9ff02bd604e1785 Mon Sep 17 00:00:00 2001 From: Lucio Franco Date: Mon, 30 Mar 2020 12:02:10 -0400 Subject: [PATCH] chore: Fix feature flags and build docs (#311) Signed-off-by: Lucio Franco --- examples/Cargo.toml | 4 +- interop/Cargo.toml | 4 +- interop/src/bin/client.rs | 4 +- interop/src/bin/server.rs | 4 +- tonic-build/src/client.rs | 9 +- tonic-build/src/lib.rs | 67 ++++++++++++-- tonic-build/src/prost.rs | 32 +++---- tonic-build/src/schema.rs | 44 --------- tonic-build/src/server.rs | 7 +- tonic-health/Cargo.toml | 3 +- tonic/Cargo.toml | 10 +-- tonic/src/codec/mod.rs | 9 +- tonic/src/codec/prost.rs | 158 ++++++++++++++++++++++++++++++++- tonic/src/codec/prost_tests.rs | 150 ------------------------------- 14 files changed, 258 insertions(+), 247 deletions(-) delete mode 100644 tonic-build/src/schema.rs delete mode 100644 tonic/src/codec/prost_tests.rs diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 58148846a..4f7232957 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tonic-examples" +name = "examples" version = "0.1.0" authors = ["Lucio Franco "] edition = "2018" @@ -107,7 +107,7 @@ name = "health-server" path = "src/health/server.rs" [dependencies] -tonic = { path = "../tonic", features = ["tls", "data-prost"] } +tonic = { path = "../tonic", features = ["tls"] } prost = "0.6" tokio = { version = "0.2", features = ["rt-threaded", "time", "stream", "fs", "macros", "uds"] } futures = { version = "0.3", default-features = false, features = ["alloc"] } diff --git a/interop/Cargo.toml b/interop/Cargo.toml index 053b6784b..58dc3b641 100644 --- a/interop/Cargo.toml +++ b/interop/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tonic-interop" +name = "interop" version = "0.1.0" authors = ["Lucio Franco "] edition = "2018" @@ -34,4 +34,4 @@ tracing-subscriber = "0.2.0-alpha" tracing-log = "0.1.0" [build-dependencies] -tonic-build = { path = "../tonic-build", features=["prost"] } +tonic-build = { path = "../tonic-build", features = ["prost"] } diff --git a/interop/src/bin/client.rs b/interop/src/bin/client.rs index f45782c2b..307548e23 100644 --- a/interop/src/bin/client.rs +++ b/interop/src/bin/client.rs @@ -1,8 +1,8 @@ +use interop::client; use std::time::Duration; use structopt::{clap::arg_enum, StructOpt}; use tonic::transport::Endpoint; use tonic::transport::{Certificate, ClientTlsConfig}; -use tonic_interop::client; #[derive(StructOpt)] struct Opts { @@ -20,7 +20,7 @@ struct Opts { #[tokio::main] async fn main() -> Result<(), Box> { - tonic_interop::trace_init(); + interop::trace_init(); let matches = Opts::from_args(); diff --git a/interop/src/bin/server.rs b/interop/src/bin/server.rs index 509e67349..35fdb1921 100644 --- a/interop/src/bin/server.rs +++ b/interop/src/bin/server.rs @@ -1,7 +1,7 @@ +use interop::server; use structopt::StructOpt; use tonic::transport::Server; use tonic::transport::{Identity, ServerTlsConfig}; -use tonic_interop::server; #[derive(StructOpt)] struct Opts { @@ -11,7 +11,7 @@ struct Opts { #[tokio::main] async fn main() -> std::result::Result<(), Box> { - tonic_interop::trace_init(); + interop::trace_init(); let matches = Opts::from_args(); diff --git a/tonic-build/src/client.rs b/tonic-build/src/client.rs index bd69b9818..bb15027b2 100644 --- a/tonic-build/src/client.rs +++ b/tonic-build/src/client.rs @@ -1,9 +1,12 @@ -use super::schema::{Method, Service}; +use super::{Method, Service}; use crate::{generate_doc_comments, naive_snake_case}; use proc_macro2::TokenStream; use quote::{format_ident, quote}; -/// Generate service for client +/// Generate service for client. +/// +/// This takes some `Service` and will generate a `TokenStream` that contains +/// a public module with the generated client. pub fn generate(service: &T, proto_path: &str) -> TokenStream { let service_ident = quote::format_ident!("{}Client", service.name()); let client_mod = quote::format_ident!("{}_client", naive_snake_case(&service.name())); @@ -80,8 +83,6 @@ fn generate_methods(service: &T, proto_path: &str) -> TokenStream { let mut stream = TokenStream::new(); for method in service.methods() { - use super::schema::Commentable; - let path = format!( "/{}.{}/{}", service.package(), diff --git a/tonic-build/src/lib.rs b/tonic-build/src/lib.rs index cc7bf23fd..0dcd71026 100644 --- a/tonic-build/src/lib.rs +++ b/tonic-build/src/lib.rs @@ -55,23 +55,25 @@ #![doc(html_root_url = "https://docs.rs/tonic-build/0.1.0")] #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] +#![cfg_attr(docsrs, feature(doc_cfg))] use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream}; use quote::TokenStreamExt; /// Prost generator #[cfg(feature = "prost")] +#[cfg_attr(docsrs, doc(cfg(feature = "prost")))] mod prost; #[cfg(feature = "prost")] +#[cfg_attr(docsrs, doc(cfg(feature = "prost")))] pub use prost::{compile_protos, configure, Builder}; -/// Traits to describe schema -pub mod schema; - #[cfg(feature = "rustfmt")] +#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] use std::io::{self, Write}; #[cfg(feature = "rustfmt")] +#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] use std::process::{exit, Command}; /// Service code generation for client @@ -79,8 +81,63 @@ pub mod client; /// Service code generation for Server pub mod server; +/// Service generation trait. +/// +/// This trait can be implemented and consumed +/// by `client::generate` and `server::generate` +/// to allow any codegen module to generate service +/// abstractions. +pub trait Service { + /// Path to the codec. + const CODEC_PATH: &'static str; + + /// Comment type. + type Comment: AsRef; + + /// Method type. + type Method: Method; + + /// 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(&self) -> &[Self::Method]; + /// Get comments about this item. + fn comment(&self) -> &[Self::Comment]; +} + +/// Method generation trait. +/// +/// Each service contains a set of generic +/// `Methods`'s that will be used by codegen +/// to generate abstraction implementations for +/// the provided methods. +pub trait Method { + /// Path to the codec. + const CODEC_PATH: &'static str; + /// Comment type. + type Comment: AsRef; + + /// 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; + /// Get comments about this item. + fn comment(&self) -> &[Self::Comment]; + /// Type name of request and response. + fn request_response_name(&self, proto_path: &str) -> (TokenStream, TokenStream); +} + /// Format files under the out_dir with rustfmt #[cfg(feature = "rustfmt")] +#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] pub fn fmt(out_dir: &str) { let dir = std::fs::read_dir(out_dir).unwrap(); @@ -129,9 +186,7 @@ fn generate_doc_comment>(comment: S) -> TokenStream { } // Generate a larger doc comment composed of many lines of doc comments -fn generate_doc_comments<'a, T: AsRef + 'a, C: IntoIterator>( - comments: C, -) -> TokenStream { +fn generate_doc_comments>(comments: &[T]) -> TokenStream { let mut stream = TokenStream::new(); for comment in comments { diff --git a/tonic-build/src/prost.rs b/tonic-build/src/prost.rs index 626435040..5531a9c0b 100644 --- a/tonic-build/src/prost.rs +++ b/tonic-build/src/prost.rs @@ -1,4 +1,4 @@ -use super::{client, schema, server}; +use super::{client, server}; use proc_macro2::TokenStream; use prost_build::{Config, Method, Service}; use quote::ToTokens; @@ -7,18 +7,11 @@ use std::path::{Path, PathBuf}; const PROST_CODEC_PATH: &'static str = "tonic::codec::ProstCodec"; -impl schema::Commentable for Service { - type Comment = String; - - fn comment(&self) -> &[Self::Comment] { - &self.comments.leading[..] - } -} - -impl schema::Service for Service { +impl crate::Service for Service { const CODEC_PATH: &'static str = PROST_CODEC_PATH; type Method = Method; + type Comment = String; fn name(&self) -> &str { &self.name @@ -32,21 +25,18 @@ impl schema::Service for Service { &self.proto_name } - fn methods(&self) -> &[Self::Method] { - &self.methods[..] - } -} - -impl schema::Commentable for Method { - type Comment = String; - fn comment(&self) -> &[Self::Comment] { &self.comments.leading[..] } + + fn methods(&self) -> &[Self::Method] { + &self.methods[..] + } } -impl schema::Method for Method { +impl crate::Method for Method { const CODEC_PATH: &'static str = PROST_CODEC_PATH; + type Comment = String; fn name(&self) -> &str { &self.name @@ -64,6 +54,10 @@ impl schema::Method for Method { self.server_streaming } + fn comment(&self) -> &[Self::Comment] { + &self.comments.leading[..] + } + fn request_response_name(&self, proto_path: &str) -> (TokenStream, TokenStream) { let request = if self.input_proto_type.starts_with(".google.protobuf") || self.input_type.starts_with("::") diff --git a/tonic-build/src/schema.rs b/tonic-build/src/schema.rs deleted file mode 100644 index 04fc108ce..000000000 --- a/tonic-build/src/schema.rs +++ /dev/null @@ -1,44 +0,0 @@ -use proc_macro2::TokenStream; - -/// Item has comment -pub trait Commentable { - /// Comment type - type Comment: AsRef; - /// Get comments about this item - fn comment(&self) -> &[Self::Comment]; -} - -/// Service -pub trait Service: Commentable { - /// Path to the codec - const CODEC_PATH: &'static str; - - /// Method type - type Method: Method; - - /// 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(&self) -> &[Self::Method]; -} - -/// Method -pub trait Method: Commentable { - /// Path to the codec - const CODEC_PATH: &'static str; - - /// 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, proto_path: &str) -> (TokenStream, TokenStream); -} diff --git a/tonic-build/src/server.rs b/tonic-build/src/server.rs index 2f804867e..5107d3e2a 100644 --- a/tonic-build/src/server.rs +++ b/tonic-build/src/server.rs @@ -1,10 +1,13 @@ -use super::schema::{Commentable, Method, Service}; +use super::{Method, Service}; use crate::{generate_doc_comment, generate_doc_comments, naive_snake_case}; use proc_macro2::{Span, TokenStream}; use quote::quote; use syn::{Ident, Lit, LitStr}; -/// Generate service for Server +/// Generate service for Server. +/// +/// This takes some `Service` and will generate a `TokenStream` that contains +/// a public module containing the server service and handler trait. pub fn generate(service: &T, proto_path: &str) -> TokenStream { let methods = generate_methods(service, proto_path); diff --git a/tonic-health/Cargo.toml b/tonic-health/Cargo.toml index b2f7645a5..dcb83f6a0 100644 --- a/tonic-health/Cargo.toml +++ b/tonic-health/Cargo.toml @@ -14,12 +14,13 @@ categories = ["network-programming", "asynchronous"] keywords = ["rpc", "grpc", "async", "healthcheck"] [features] +default = ["transport"] transport = ["tonic/transport"] [dependencies] async-stream = "0.2" tokio = { version = "0.2", features = ["sync", "stream"] } -tonic = { version = "0.1", path = "../tonic", features = ["codegen", "data-prost"] } +tonic = { version = "0.1", path = "../tonic", features = ["codegen", "prost"] } bytes = "0.5" prost = "0.6" diff --git a/tonic/Cargo.toml b/tonic/Cargo.toml index 61fdb725c..69c39a790 100644 --- a/tonic/Cargo.toml +++ b/tonic/Cargo.toml @@ -23,7 +23,7 @@ categories = ["web-programming", "network-programming", "asynchronous"] keywords = ["rpc", "grpc", "async", "futures", "protobuf"] [features] -default = ["transport", "codegen", "data-prost"] +default = ["transport", "codegen", "prost"] codegen = ["async-trait"] transport = [ "hyper", @@ -35,7 +35,7 @@ transport = [ ] tls = ["transport", "tokio-rustls"] tls-roots = ["tls", "rustls-native-certs"] -data-prost = ["prost", "prost-derive"] +prost = ["prost1", "prost-derive"] # [[bench]] # name = "bench_main" @@ -57,15 +57,15 @@ http-body = "0.3" pin-project = "0.4" # prost -prost = { version = "0.6", optional = true } +prost1 = { package = "prost", version = "0.6", optional = true } prost-derive = { version = "0.6", optional = true } # codegen async-trait = { version = "0.1.13", optional = true } # transport -hyper = { version = "0.13", features = ["stream"], optional = true } -tokio = { version = "0.2", features = ["tcp"], optional = true } +hyper = { version = "0.13.4", features = ["stream"], optional = true } +tokio = { version = "0.2.13", features = ["tcp"], optional = true } tower = { version = "0.3", optional = true} tower-make = { version = "0.3", features = ["connect"] } tower-balance = { version = "0.3", optional = true } diff --git a/tonic/src/codec/mod.rs b/tonic/src/codec/mod.rs index b6da55d7c..e100556c3 100644 --- a/tonic/src/codec/mod.rs +++ b/tonic/src/codec/mod.rs @@ -6,18 +6,15 @@ mod buffer; mod decode; mod encode; -#[cfg(feature = "data-prost")] +#[cfg(feature = "prost")] mod prost; -#[cfg(all(test, feature = "data-prost"))] -mod prost_tests; - use std::io; pub use self::decode::Streaming; pub(crate) use self::encode::{encode_client, encode_server}; -#[cfg(feature = "data-prost")] -#[cfg_attr(docsrs, doc(cfg(feature = "data-prost")))] +#[cfg(feature = "prost")] +#[cfg_attr(docsrs, doc(cfg(feature = "prost")))] pub use self::prost::ProstCodec; use crate::Status; pub use buffer::{DecodeBuf, EncodeBuf}; diff --git a/tonic/src/codec/prost.rs b/tonic/src/codec/prost.rs index d23521739..2135b8211 100644 --- a/tonic/src/codec/prost.rs +++ b/tonic/src/codec/prost.rs @@ -1,7 +1,7 @@ use super::{Codec, DecodeBuf, Decoder, Encoder}; use crate::codec::EncodeBuf; use crate::{Code, Status}; -use prost::Message; +use prost1::Message; use std::marker::PhantomData; /// A [`Codec`] that implements `application/grpc+proto` via the prost library.. @@ -69,8 +69,162 @@ impl Decoder for ProstDecoder { } } -fn from_decode_error(error: prost::DecodeError) -> crate::Status { +fn from_decode_error(error: prost1::DecodeError) -> crate::Status { // Map Protobuf parse errors to an INTERNAL status code, as per // https://github.com/grpc/grpc/blob/master/doc/statuscodes.md Status::new(Code::Internal, error.to_string()) } + +#[cfg(tests)] +mod tests { + use super::{encode_server, Decoder, Encoder, Streaming}; + use crate::codec::buffer::DecodeBuf; + use crate::codec::EncodeBuf; + use crate::Status; + use bytes::{Buf, BufMut, BytesMut}; + use http_body::Body; + + const LEN: usize = 10000; + + #[tokio::test] + async fn decode() { + let decoder = MockDecoder::default(); + + let msg = vec![0u8; LEN]; + + let mut buf = BytesMut::new(); + + buf.reserve(msg.len() + 5); + buf.put_u8(0); + buf.put_u32(msg.len() as u32); + + buf.put(&msg[..]); + + let body = body::MockBody::new(&buf[..], 10005, 0); + + let mut stream = Streaming::new_request(decoder, body); + + let mut i = 0usize; + while let Some(output_msg) = stream.message().await.unwrap() { + assert_eq!(output_msg.len(), msg.len()); + i += 1; + } + assert_eq!(i, 1); + } + + #[tokio::test] + async fn encode() { + let encoder = MockEncoder::default(); + + let msg = Vec::from(&[0u8; 1024][..]); + + let messages = std::iter::repeat(Ok::<_, Status>(msg)).take(10000); + let source = futures_util::stream::iter(messages); + + let body = encode_server(encoder, source); + + futures_util::pin_mut!(body); + + while let Some(r) = body.data().await { + r.unwrap(); + } + } + + #[derive(Debug, Clone, Default)] + struct MockEncoder; + + impl Encoder for MockEncoder { + type Item = Vec; + type Error = Status; + + fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { + buf.put(&item[..]); + Ok(()) + } + } + + #[derive(Debug, Clone, Default)] + struct MockDecoder; + + impl Decoder for MockDecoder { + type Item = Vec; + type Error = Status; + + fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { + let out = Vec::from(buf.bytes()); + buf.advance(LEN); + Ok(Some(out)) + } + } + + mod body { + use crate::Status; + use bytes::Bytes; + use http_body::Body; + use std::{ + pin::Pin, + task::{Context, Poll}, + }; + + #[derive(Debug)] + pub(super) struct MockBody { + data: Bytes, + + // the size of the partial message to send + partial_len: usize, + + // the number of times we've sent + count: usize, + } + + impl MockBody { + pub(super) fn new(b: &[u8], partial_len: usize, count: usize) -> Self { + MockBody { + data: Bytes::copy_from_slice(&b[..]), + partial_len, + count, + } + } + } + + impl Body for MockBody { + type Data = Bytes; + type Error = Status; + + fn poll_data( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + // every other call to poll_data returns data + let should_send = self.count % 2 == 0; + let data_len = self.data.len(); + let partial_len = self.partial_len; + let count = self.count; + if data_len > 0 { + let result = if should_send { + let response = + self.data + .split_to(if count == 0 { partial_len } else { data_len }); + Poll::Ready(Some(Ok(response))) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + }; + // make some fake progress + self.count += 1; + result + } else { + Poll::Ready(None) + } + } + + fn poll_trailers( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll, Self::Error>> { + drop(cx); + Poll::Ready(Ok(None)) + } + } + } +} diff --git a/tonic/src/codec/prost_tests.rs b/tonic/src/codec/prost_tests.rs deleted file mode 100644 index 09c65676b..000000000 --- a/tonic/src/codec/prost_tests.rs +++ /dev/null @@ -1,150 +0,0 @@ -use super::{encode_server, Decoder, Encoder, Streaming}; -use crate::codec::buffer::DecodeBuf; -use crate::codec::EncodeBuf; -use crate::Status; -use bytes::{Buf, BufMut, BytesMut}; -use http_body::Body; - -const LEN: usize = 10000; - -#[tokio::test] -async fn decode() { - let decoder = MockDecoder::default(); - - let msg = vec![0u8; LEN]; - - let mut buf = BytesMut::new(); - - buf.reserve(msg.len() + 5); - buf.put_u8(0); - buf.put_u32(msg.len() as u32); - - buf.put(&msg[..]); - - let body = body::MockBody::new(&buf[..], 10005, 0); - - let mut stream = Streaming::new_request(decoder, body); - - let mut i = 0usize; - while let Some(output_msg) = stream.message().await.unwrap() { - assert_eq!(output_msg.len(), msg.len()); - i += 1; - } - assert_eq!(i, 1); -} - -#[tokio::test] -async fn encode() { - let encoder = MockEncoder::default(); - - let msg = Vec::from(&[0u8; 1024][..]); - - let messages = std::iter::repeat(Ok::<_, Status>(msg)).take(10000); - let source = futures_util::stream::iter(messages); - - let body = encode_server(encoder, source); - - futures_util::pin_mut!(body); - - while let Some(r) = body.data().await { - r.unwrap(); - } -} - -#[derive(Debug, Clone, Default)] -struct MockEncoder; - -impl Encoder for MockEncoder { - type Item = Vec; - type Error = Status; - - fn encode(&mut self, item: Self::Item, buf: &mut EncodeBuf<'_>) -> Result<(), Self::Error> { - buf.put(&item[..]); - Ok(()) - } -} - -#[derive(Debug, Clone, Default)] -struct MockDecoder; - -impl Decoder for MockDecoder { - type Item = Vec; - type Error = Status; - - fn decode(&mut self, buf: &mut DecodeBuf<'_>) -> Result, Self::Error> { - let out = Vec::from(buf.bytes()); - buf.advance(LEN); - Ok(Some(out)) - } -} - -mod body { - use crate::Status; - use bytes::Bytes; - use http_body::Body; - use std::{ - pin::Pin, - task::{Context, Poll}, - }; - - #[derive(Debug)] - pub(super) struct MockBody { - data: Bytes, - - // the size of the partial message to send - partial_len: usize, - - // the number of times we've sent - count: usize, - } - - impl MockBody { - pub(super) fn new(b: &[u8], partial_len: usize, count: usize) -> Self { - MockBody { - data: Bytes::copy_from_slice(&b[..]), - partial_len, - count, - } - } - } - - impl Body for MockBody { - type Data = Bytes; - type Error = Status; - - fn poll_data( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - // every other call to poll_data returns data - let should_send = self.count % 2 == 0; - let data_len = self.data.len(); - let partial_len = self.partial_len; - let count = self.count; - if data_len > 0 { - let result = if should_send { - let response = - self.data - .split_to(if count == 0 { partial_len } else { data_len }); - Poll::Ready(Some(Ok(response))) - } else { - cx.waker().wake_by_ref(); - Poll::Pending - }; - // make some fake progress - self.count += 1; - result - } else { - Poll::Ready(None) - } - } - - fn poll_trailers( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll, Self::Error>> { - drop(cx); - Poll::Ready(Ok(None)) - } - } -}