Skip to content

Commit 0585ada

Browse files
committed
feat(build): Add 'use_generic_streaming_requests'
Adds an option for server trait generation that causes client-streaming methods to receive a generic `Request<impl Stream>` param instead of a concrete `Request<Streaming<...>>` param. This allows for testing trait implementions with any type implementing `Stream`, and not having to go through the trouble of encoding a `Streaming` object. Fixes #462
1 parent f9ccc27 commit 0585ada

File tree

9 files changed

+144
-7
lines changed

9 files changed

+144
-7
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ members = [
2525
"tests/web",
2626
"tests/service_named_result",
2727
"tests/use_arc_self",
28+
"tests/use_generic_streaming_requests",
2829
"tests/default_stubs",
2930
"tests/deprecated_methods",
3031
"tests/skip_debug",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
[package]
2+
authors = ["Yotam Ofek <[email protected]>"]
3+
edition = "2021"
4+
license = "MIT"
5+
name = "use_generic_streaming_requests"
6+
7+
[dependencies]
8+
tokio-stream = "0.1"
9+
prost = "0.13"
10+
tonic = {path = "../../tonic"}
11+
tokio = {version = "1.0", features = ["macros"]}
12+
13+
[build-dependencies]
14+
tonic-build = {path = "../../tonic-build" }
15+
16+
[package.metadata.cargo-machete]
17+
ignored = ["prost"]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
Copyright (c) 2020 Lucio Franco
2+
3+
Permission is hereby granted, free of charge, to any person obtaining a copy
4+
of this software and associated documentation files (the "Software"), to deal
5+
in the Software without restriction, including without limitation the rights
6+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7+
copies of the Software, and to permit persons to whom the Software is
8+
furnished to do so, subject to the following conditions:
9+
10+
The above copyright notice and this permission notice shall be included in
11+
all copies or substantial portions of the Software.
12+
13+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19+
THE SOFTWARE.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
fn main() {
2+
tonic_build::configure()
3+
.use_generic_streaming_requests(true)
4+
.compile_protos(&["proto/test.proto"], &["proto"])
5+
.unwrap();
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
syntax = "proto3";
2+
3+
package test;
4+
5+
service Test {
6+
rpc TestRequest(stream Message) returns (Message);
7+
}
8+
9+
message Message {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
use tokio_stream::StreamExt;
2+
use tonic::{Response, Status};
3+
4+
tonic::include_proto!("test");
5+
6+
#[derive(Debug, Default)]
7+
pub struct Svc;
8+
9+
#[tonic::async_trait]
10+
impl test_server::Test for Svc {
11+
async fn test_request(
12+
&self,
13+
req: tonic::Request<
14+
impl tokio_stream::Stream<Item = Result<Message, Status>> + Send + Unpin,
15+
>,
16+
) -> Result<Response<Message>, Status> {
17+
let mut req = req.into_inner();
18+
while let Some(message) = req.try_next().await? {
19+
println!("Got message: {message:?}")
20+
}
21+
22+
Ok(Response::new(Message {}))
23+
}
24+
}
25+
26+
#[cfg(test)]
27+
mod tests {
28+
use tonic::Request;
29+
30+
use super::test_server::Test;
31+
use super::*;
32+
33+
#[tokio::test]
34+
async fn test_request_handler() {
35+
let incoming_messages = tokio_stream::iter([Message {}, Message {}].map(Ok));
36+
let svc = Svc;
37+
svc.test_request(Request::new(incoming_messages))
38+
.await
39+
.unwrap();
40+
}
41+
}

tonic-build/src/code_gen.rs

+16
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ pub struct CodeGenBuilder {
1414
disable_comments: HashSet<String>,
1515
use_arc_self: bool,
1616
generate_default_stubs: bool,
17+
use_generic_streaming_requests: bool,
1718
}
1819

1920
impl CodeGenBuilder {
@@ -71,6 +72,19 @@ impl CodeGenBuilder {
7172
self
7273
}
7374

75+
/// Enable or disable using `Request<impl Stream>` instead of `Request<Streaming<Message>>`
76+
/// as the parameter type for generated trait methods of client-streaming functions.
77+
///
78+
/// This allows calling those trait methods with a `Request` containing any object that implements
79+
/// `Stream<Item = Result<Message, Status>>`, which can be helpful for testing request handler logic.
80+
pub fn use_generic_streaming_requests(
81+
&mut self,
82+
use_generic_streaming_requests: bool,
83+
) -> &mut Self {
84+
self.use_generic_streaming_requests = use_generic_streaming_requests;
85+
self
86+
}
87+
7488
/// Generate client code based on `Service`.
7589
///
7690
/// This takes some `Service` and will generate a `TokenStream` that contains
@@ -101,6 +115,7 @@ impl CodeGenBuilder {
101115
&self.disable_comments,
102116
self.use_arc_self,
103117
self.generate_default_stubs,
118+
self.use_generic_streaming_requests,
104119
)
105120
}
106121
}
@@ -115,6 +130,7 @@ impl Default for CodeGenBuilder {
115130
disable_comments: HashSet::default(),
116131
use_arc_self: false,
117132
generate_default_stubs: false,
133+
use_generic_streaming_requests: false,
118134
}
119135
}
120136
}

tonic-build/src/prost.rs

+15
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ pub fn configure() -> Builder {
4141
disable_comments: HashSet::default(),
4242
use_arc_self: false,
4343
generate_default_stubs: false,
44+
use_generic_streaming_requests: false,
4445
compile_settings: CompileSettings::default(),
4546
skip_debug: HashSet::default(),
4647
}
@@ -228,6 +229,7 @@ impl prost_build::ServiceGenerator for ServiceGenerator {
228229
.disable_comments(self.builder.disable_comments.clone())
229230
.use_arc_self(self.builder.use_arc_self)
230231
.generate_default_stubs(self.builder.generate_default_stubs)
232+
.use_generic_streaming_requests(self.builder.use_generic_streaming_requests)
231233
.generate_server(
232234
&TonicBuildService::new(service.clone(), self.builder.compile_settings.clone()),
233235
&self.builder.proto_path,
@@ -310,6 +312,7 @@ pub struct Builder {
310312
pub(crate) disable_comments: HashSet<String>,
311313
pub(crate) use_arc_self: bool,
312314
pub(crate) generate_default_stubs: bool,
315+
pub(crate) use_generic_streaming_requests: bool,
313316
pub(crate) compile_settings: CompileSettings,
314317
pub(crate) skip_debug: HashSet<String>,
315318

@@ -584,6 +587,18 @@ impl Builder {
584587
self
585588
}
586589

590+
/// Enable or disable using `Request<impl Stream>` instead of `Request<Streaming<Message>>`
591+
/// as the parameter type for generated trait methods of client-streaming functions.
592+
///
593+
/// This allows calling those trait methods with a `Request` containing any object that implements
594+
/// `Stream<Item = Result<Message, Status>>`, which can be helpful for testing request handler logic.
595+
///
596+
/// This defaults to `false`.
597+
pub fn use_generic_streaming_requests(mut self, use_generic_streaming_requests: bool) -> Self {
598+
self.use_generic_streaming_requests = use_generic_streaming_requests;
599+
self
600+
}
601+
587602
/// Override the default codec.
588603
///
589604
/// If set, writes `{codec_path}::default()` in generated code wherever a codec is created.

tonic-build/src/server.rs

+20-7
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ pub(crate) fn generate_internal<T: Service>(
1919
disable_comments: &HashSet<String>,
2020
use_arc_self: bool,
2121
generate_default_stubs: bool,
22+
use_generic_streaming_requests: bool,
2223
) -> TokenStream {
2324
let methods = generate_methods(
2425
service,
@@ -41,6 +42,7 @@ pub(crate) fn generate_internal<T: Service>(
4142
disable_comments,
4243
use_arc_self,
4344
generate_default_stubs,
45+
use_generic_streaming_requests,
4446
);
4547
let package = if emit_package { service.package() } else { "" };
4648
// Transport based implementations
@@ -203,6 +205,7 @@ fn generate_trait<T: Service>(
203205
disable_comments: &HashSet<String>,
204206
use_arc_self: bool,
205207
generate_default_stubs: bool,
208+
use_generic_streaming_requests: bool,
206209
) -> TokenStream {
207210
let methods = generate_trait_methods(
208211
service,
@@ -212,6 +215,7 @@ fn generate_trait<T: Service>(
212215
disable_comments,
213216
use_arc_self,
214217
generate_default_stubs,
218+
use_generic_streaming_requests,
215219
);
216220
let trait_doc = generate_doc_comment(format!(
217221
" Generated trait containing gRPC methods that should be implemented for use with {}Server.",
@@ -227,6 +231,7 @@ fn generate_trait<T: Service>(
227231
}
228232
}
229233

234+
#[allow(clippy::too_many_arguments)]
230235
fn generate_trait_methods<T: Service>(
231236
service: &T,
232237
emit_package: bool,
@@ -235,6 +240,7 @@ fn generate_trait_methods<T: Service>(
235240
disable_comments: &HashSet<String>,
236241
use_arc_self: bool,
237242
generate_default_stubs: bool,
243+
use_generic_streaming_requests: bool,
238244
) -> TokenStream {
239245
let mut stream = TokenStream::new();
240246

@@ -257,10 +263,20 @@ fn generate_trait_methods<T: Service>(
257263
quote!(&self)
258264
};
259265

260-
let req_param_type = if method.client_streaming() {
261-
quote!(tonic::Request<tonic::Streaming<#req_message>>)
262-
} else {
263-
quote!(tonic::Request<#req_message>)
266+
let result = |ok| quote!(std::result::Result<#ok, tonic::Status>);
267+
let response_result = |message| result(quote!(tonic::Response<#message>));
268+
269+
let req_param_type = {
270+
let inner_ty = if !method.client_streaming() {
271+
req_message
272+
} else if !use_generic_streaming_requests {
273+
quote!(tonic::Streaming<#req_message>)
274+
} else {
275+
let message_ty = result(req_message);
276+
quote!(impl tokio_stream::Stream<Item = #message_ty> + std::marker::Send + std::marker::Unpin)
277+
};
278+
279+
quote!(tonic::Request<#inner_ty>)
264280
};
265281

266282
let partial_sig = quote! {
@@ -278,9 +294,6 @@ fn generate_trait_methods<T: Service>(
278294
quote!(;)
279295
};
280296

281-
let result = |ok| quote!(std::result::Result<#ok, tonic::Status>);
282-
let response_result = |message| result(quote!(tonic::Response<#message>));
283-
284297
let method = if !method.server_streaming() {
285298
let return_ty = response_result(res_message);
286299
quote! {

0 commit comments

Comments
 (0)