diff --git a/tonic-build/src/client.rs b/tonic-build/src/client.rs index b19dbbbae..62b668e84 100644 --- a/tonic-build/src/client.rs +++ b/tonic-build/src/client.rs @@ -99,12 +99,14 @@ fn generate_unary(method: &Method, proto: &str, path: String) -> TokenStream { let (request, response) = crate::replace_wellknown(proto, &method); quote! { - pub async fn #ident(&mut self, request: tonic::Request<#request>) - -> Result, tonic::Status> { + pub async fn #ident( + &mut self, + request: impl tonic::IntoRequest<#request>, + ) -> Result, tonic::Status> { self.ready().await?; let codec = tonic::codec::ProstCodec::new(); let path = http::uri::PathAndQuery::from_static(#path); - self.inner.unary(request, path, codec).await + self.inner.unary(request.into_request(), path, codec).await } } } @@ -115,12 +117,14 @@ fn generate_server_streaming(method: &Method, proto: &str, path: String) -> Toke let (request, response) = crate::replace_wellknown(proto, &method); quote! { - pub async fn #ident(&mut self, request: tonic::Request<#request>) - -> Result>, tonic::Status> { + pub async fn #ident( + &mut self, + request: impl tonic::IntoRequest<#request>, + ) -> Result>, tonic::Status> { self.ready().await?; let codec = tonic::codec::ProstCodec::new(); let path = http::uri::PathAndQuery::from_static(#path); - self.inner.server_streaming(request, path, codec).await + self.inner.server_streaming(request.into_request(), path, codec).await } } } @@ -131,14 +135,14 @@ fn generate_client_streaming(method: &Method, proto: &str, path: String) -> Toke let (request, response) = crate::replace_wellknown(proto, &method); quote! { - pub async fn #ident(&mut self, request: tonic::Request) - -> Result, tonic::Status> - where S: Stream + Send + 'static, - { + pub async fn #ident( + &mut self, + request: impl tonic::IntoStreamingRequest + ) -> Result, tonic::Status> { self.ready().await?; let codec = tonic::codec::ProstCodec::new(); let path = http::uri::PathAndQuery::from_static(#path); - self.inner.client_streaming(request, path, codec).await + self.inner.client_streaming(request.into_streaming_request(), path, codec).await } } } @@ -149,14 +153,14 @@ fn generate_streaming(method: &Method, proto: &str, path: String) -> TokenStream let (request, response) = crate::replace_wellknown(proto, &method); quote! { - pub async fn #ident(&mut self, request: tonic::Request) - -> Result>, tonic::Status> - where S: Stream + Send + 'static, - { + pub async fn #ident( + &mut self, + request: impl tonic::IntoStreamingRequest + ) -> Result>, tonic::Status> { self.ready().await?; let codec = tonic::codec::ProstCodec::new(); let path = http::uri::PathAndQuery::from_static(#path); - self.inner.streaming(request, path, codec).await + self.inner.streaming(request.into_streaming_request(), path, codec).await } } } diff --git a/tonic/src/lib.rs b/tonic/src/lib.rs index 225d158db..af5c672ba 100644 --- a/tonic/src/lib.rs +++ b/tonic/src/lib.rs @@ -98,7 +98,7 @@ pub use async_trait::async_trait; #[doc(inline)] pub use codec::Streaming; -pub use request::Request; +pub use request::{IntoRequest, IntoStreamingRequest, Request}; pub use response::Response; pub use status::{Code, Status}; diff --git a/tonic/src/request.rs b/tonic/src/request.rs index 2a46f1004..2042e135f 100644 --- a/tonic/src/request.rs +++ b/tonic/src/request.rs @@ -1,4 +1,5 @@ use crate::metadata::MetadataMap; +use futures_core::Stream; /// A gRPC request and metadata from an RPC call. #[derive(Debug)] @@ -7,6 +8,84 @@ pub struct Request { message: T, } +/// Trait implemented by RPC request types. +/// +/// Types implementing this trait can be used as arguments to client RPC +/// methods without explicitly wrapping them into `tonic::Request`s. The purpose +/// is to make client calls slightly more convenient to write. +/// +/// Tonic's code generation and blanket implementations handle this for you, +/// so it is not necessary to implement this trait directly. +/// +/// # Example +/// +/// Given the following gRPC method definition: +/// ```proto +/// rpc GetFeature(Point) returns (Feature) {} +/// ``` +/// +/// we can call `get_feature` in two equivalent ways: +/// ```rust +/// # pub struct Point {} +/// # pub struct Client {} +/// # impl Client { +/// # fn get_feature(&self, r: impl tonic::IntoRequest) {} +/// # } +/// # let client = Client {}; +/// use tonic::Request; +/// +/// client.get_feature(Point {}); +/// client.get_feature(Request::new(Point {})); +/// ``` +pub trait IntoRequest: sealed::Sealed { + /// Wrap the input message `T` in a `tonic::Request` + fn into_request(self) -> Request; +} + +/// Trait implemented by RPC streaming request types. +/// +/// Types implementing this trait can be used as arguments to client streaming +/// RPC methods without explicitly wrapping them into `tonic::Request`s. The +/// purpose is to make client calls slightly more convenient to write. +/// +/// Tonic's code generation and blanket implementations handle this for you, +/// so it is not necessary to implement this trait directly. +/// +/// # Example +/// +/// Given the following gRPC service method definition: +/// ```proto +/// rpc RecordRoute(stream Point) returns (RouteSummary) {} +/// ``` +/// we can call `record_route` in two equivalent ways: +/// +/// ```rust +/// # #[derive(Clone)] +/// # pub struct Point {}; +/// # pub struct Client {}; +/// # impl Client { +/// # fn record_route(&self, r: impl tonic::IntoStreamingRequest) {} +/// # } +/// # let client = Client {}; +/// use tonic::Request; +/// use futures_util::stream; +/// +/// let messages = vec![Point {}, Point {}]; +/// +/// client.record_route(Request::new(stream::iter(messages.clone()))); +/// client.record_route(stream::iter(messages)); +/// ``` +pub trait IntoStreamingRequest: sealed::Sealed { + /// The RPC request stream type + type Stream: Stream + Send + 'static; + + /// The RPC request type + type Message; + + /// Wrap the stream of messages in a `tonic::Request` + fn into_streaming_request(self) -> Request; +} + impl Request { /// Create a new gRPC request. /// @@ -88,3 +167,45 @@ impl Request { } } } + +impl IntoRequest for T { + fn into_request(self) -> Request { + Request::new(self) + } +} + +impl IntoRequest for Request { + fn into_request(self) -> Request { + self + } +} + +impl IntoStreamingRequest for T +where + T: Stream + Send + 'static, +{ + type Stream = T; + type Message = T::Item; + + fn into_streaming_request(self) -> Request { + Request::new(self) + } +} + +impl IntoStreamingRequest for Request +where + T: Stream + Send + 'static, +{ + type Stream = T; + type Message = T::Item; + + fn into_streaming_request(self) -> Self { + self + } +} + +impl sealed::Sealed for T {} + +mod sealed { + pub trait Sealed {} +}