xref: /tonic/tonic-build/src/client.rs (revision 60b131d2)
1 use std::collections::HashSet;
2 
3 use super::{Attributes, Method, Service};
4 use crate::{
5     format_method_name, format_method_path, format_service_name, generate_deprecated,
6     generate_doc_comments, naive_snake_case,
7 };
8 use proc_macro2::TokenStream;
9 use quote::{format_ident, quote};
10 
generate_internal<T: Service>( service: &T, emit_package: bool, proto_path: &str, compile_well_known_types: bool, build_transport: bool, attributes: &Attributes, disable_comments: &HashSet<String>, ) -> TokenStream11 pub(crate) fn generate_internal<T: Service>(
12     service: &T,
13     emit_package: bool,
14     proto_path: &str,
15     compile_well_known_types: bool,
16     build_transport: bool,
17     attributes: &Attributes,
18     disable_comments: &HashSet<String>,
19 ) -> TokenStream {
20     let service_ident = quote::format_ident!("{}Client", service.name());
21     let client_mod = quote::format_ident!("{}_client", naive_snake_case(service.name()));
22     let methods = generate_methods(
23         service,
24         emit_package,
25         proto_path,
26         compile_well_known_types,
27         disable_comments,
28     );
29 
30     let connect = generate_connect(&service_ident, build_transport);
31 
32     let package = if emit_package { service.package() } else { "" };
33     let service_name = format_service_name(service, emit_package);
34 
35     let service_doc = if disable_comments.contains(&service_name) {
36         TokenStream::new()
37     } else {
38         generate_doc_comments(service.comment())
39     };
40 
41     let mod_attributes = attributes.for_mod(package);
42     let struct_attributes = attributes.for_struct(&service_name);
43 
44     quote! {
45         /// Generated client implementations.
46         #(#mod_attributes)*
47         pub mod #client_mod {
48             #![allow(
49                 unused_variables,
50                 dead_code,
51                 missing_docs,
52                 clippy::wildcard_imports,
53                 // will trigger if compression is disabled
54                 clippy::let_unit_value,
55             )]
56             use tonic::codegen::*;
57             use tonic::codegen::http::Uri;
58 
59             #service_doc
60             #(#struct_attributes)*
61             #[derive(Debug, Clone)]
62             pub struct #service_ident<T> {
63                 inner: tonic::client::Grpc<T>,
64             }
65 
66             #connect
67 
68             impl<T> #service_ident<T>
69             where
70                 T: tonic::client::GrpcService<tonic::body::Body>,
71                 T::Error: Into<StdError>,
72                 T::ResponseBody: Body<Data = Bytes> + std::marker::Send  + 'static,
73                 <T::ResponseBody as Body>::Error: Into<StdError> + std::marker::Send,
74             {
75                 pub fn new(inner: T) -> Self {
76                     let inner = tonic::client::Grpc::new(inner);
77                     Self { inner }
78                 }
79 
80                 pub fn with_origin(inner: T, origin: Uri) -> Self {
81                     let inner = tonic::client::Grpc::with_origin(inner, origin);
82                     Self { inner }
83                 }
84 
85                 pub fn with_interceptor<F>(inner: T, interceptor: F) -> #service_ident<InterceptedService<T, F>>
86                 where
87                     F: tonic::service::Interceptor,
88                     T::ResponseBody: Default,
89                     T: tonic::codegen::Service<
90                         http::Request<tonic::body::Body>,
91                         Response = http::Response<<T as tonic::client::GrpcService<tonic::body::Body>>::ResponseBody>
92                     >,
93                     <T as tonic::codegen::Service<http::Request<tonic::body::Body>>>::Error: Into<StdError> + std::marker::Send + std::marker::Sync,
94                 {
95                     #service_ident::new(InterceptedService::new(inner, interceptor))
96                 }
97 
98                 /// Compress requests with the given encoding.
99                 ///
100                 /// This requires the server to support it otherwise it might respond with an
101                 /// error.
102                 #[must_use]
103                 pub fn send_compressed(mut self, encoding: CompressionEncoding) -> Self {
104                     self.inner = self.inner.send_compressed(encoding);
105                     self
106                 }
107 
108                 /// Enable decompressing responses.
109                 #[must_use]
110                 pub fn accept_compressed(mut self, encoding: CompressionEncoding) -> Self {
111                     self.inner = self.inner.accept_compressed(encoding);
112                     self
113                 }
114 
115                 /// Limits the maximum size of a decoded message.
116                 ///
117                 /// Default: `4MB`
118                 #[must_use]
119                 pub fn max_decoding_message_size(mut self, limit: usize) -> Self {
120                     self.inner = self.inner.max_decoding_message_size(limit);
121                     self
122                 }
123 
124                 /// Limits the maximum size of an encoded message.
125                 ///
126                 /// Default: `usize::MAX`
127                 #[must_use]
128                 pub fn max_encoding_message_size(mut self, limit: usize) -> Self {
129                     self.inner = self.inner.max_encoding_message_size(limit);
130                     self
131                 }
132 
133                 #methods
134             }
135         }
136     }
137 }
138 
139 #[cfg(feature = "transport")]
generate_connect(service_ident: &syn::Ident, enabled: bool) -> TokenStream140 fn generate_connect(service_ident: &syn::Ident, enabled: bool) -> TokenStream {
141     let connect_impl = quote! {
142         impl #service_ident<tonic::transport::Channel> {
143             /// Attempt to create a new client by connecting to a given endpoint.
144             pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
145             where
146                 D: TryInto<tonic::transport::Endpoint>,
147                 D::Error: Into<StdError>,
148             {
149                 let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
150                 Ok(Self::new(conn))
151             }
152         }
153     };
154 
155     if enabled {
156         connect_impl
157     } else {
158         TokenStream::new()
159     }
160 }
161 
162 #[cfg(not(feature = "transport"))]
generate_connect(_service_ident: &syn::Ident, _enabled: bool) -> TokenStream163 fn generate_connect(_service_ident: &syn::Ident, _enabled: bool) -> TokenStream {
164     TokenStream::new()
165 }
166 
generate_methods<T: Service>( service: &T, emit_package: bool, proto_path: &str, compile_well_known_types: bool, disable_comments: &HashSet<String>, ) -> TokenStream167 fn generate_methods<T: Service>(
168     service: &T,
169     emit_package: bool,
170     proto_path: &str,
171     compile_well_known_types: bool,
172     disable_comments: &HashSet<String>,
173 ) -> TokenStream {
174     let mut stream = TokenStream::new();
175 
176     for method in service.methods() {
177         if !disable_comments.contains(&format_method_name(service, method, emit_package)) {
178             stream.extend(generate_doc_comments(method.comment()));
179         }
180         if method.deprecated() {
181             stream.extend(generate_deprecated());
182         }
183 
184         let method = match (method.client_streaming(), method.server_streaming()) {
185             (false, false) => generate_unary(
186                 service,
187                 method,
188                 emit_package,
189                 proto_path,
190                 compile_well_known_types,
191             ),
192             (false, true) => generate_server_streaming(
193                 service,
194                 method,
195                 emit_package,
196                 proto_path,
197                 compile_well_known_types,
198             ),
199             (true, false) => generate_client_streaming(
200                 service,
201                 method,
202                 emit_package,
203                 proto_path,
204                 compile_well_known_types,
205             ),
206             (true, true) => generate_streaming(
207                 service,
208                 method,
209                 emit_package,
210                 proto_path,
211                 compile_well_known_types,
212             ),
213         };
214 
215         stream.extend(method);
216     }
217 
218     stream
219 }
220 
generate_unary<T: Service>( service: &T, method: &T::Method, emit_package: bool, proto_path: &str, compile_well_known_types: bool, ) -> TokenStream221 fn generate_unary<T: Service>(
222     service: &T,
223     method: &T::Method,
224     emit_package: bool,
225     proto_path: &str,
226     compile_well_known_types: bool,
227 ) -> TokenStream {
228     let codec_name = syn::parse_str::<syn::Path>(method.codec_path()).unwrap();
229     let ident = format_ident!("{}", method.name());
230     let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
231     let service_name = format_service_name(service, emit_package);
232     let path = format_method_path(service, method, emit_package);
233     let method_name = method.identifier();
234 
235     quote! {
236         pub async fn #ident(
237             &mut self,
238             request: impl tonic::IntoRequest<#request>,
239         ) -> std::result::Result<tonic::Response<#response>, tonic::Status> {
240            self.inner.ready().await.map_err(|e| {
241                tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
242            })?;
243            let codec = #codec_name::default();
244            let path = http::uri::PathAndQuery::from_static(#path);
245            let mut req = request.into_request();
246            req.extensions_mut().insert(GrpcMethod::new(#service_name, #method_name));
247            self.inner.unary(req, path, codec).await
248         }
249     }
250 }
251 
generate_server_streaming<T: Service>( service: &T, method: &T::Method, emit_package: bool, proto_path: &str, compile_well_known_types: bool, ) -> TokenStream252 fn generate_server_streaming<T: Service>(
253     service: &T,
254     method: &T::Method,
255     emit_package: bool,
256     proto_path: &str,
257     compile_well_known_types: bool,
258 ) -> TokenStream {
259     let codec_name = syn::parse_str::<syn::Path>(method.codec_path()).unwrap();
260     let ident = format_ident!("{}", method.name());
261     let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
262     let service_name = format_service_name(service, emit_package);
263     let path = format_method_path(service, method, emit_package);
264     let method_name = method.identifier();
265 
266     quote! {
267         pub async fn #ident(
268             &mut self,
269             request: impl tonic::IntoRequest<#request>,
270         ) -> std::result::Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
271             self.inner.ready().await.map_err(|e| {
272                 tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
273             })?;
274             let codec = #codec_name::default();
275             let path = http::uri::PathAndQuery::from_static(#path);
276             let mut req = request.into_request();
277             req.extensions_mut().insert(GrpcMethod::new(#service_name, #method_name));
278             self.inner.server_streaming(req, path, codec).await
279         }
280     }
281 }
282 
generate_client_streaming<T: Service>( service: &T, method: &T::Method, emit_package: bool, proto_path: &str, compile_well_known_types: bool, ) -> TokenStream283 fn generate_client_streaming<T: Service>(
284     service: &T,
285     method: &T::Method,
286     emit_package: bool,
287     proto_path: &str,
288     compile_well_known_types: bool,
289 ) -> TokenStream {
290     let codec_name = syn::parse_str::<syn::Path>(method.codec_path()).unwrap();
291     let ident = format_ident!("{}", method.name());
292     let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
293     let service_name = format_service_name(service, emit_package);
294     let path = format_method_path(service, method, emit_package);
295     let method_name = method.identifier();
296 
297     quote! {
298         pub async fn #ident(
299             &mut self,
300             request: impl tonic::IntoStreamingRequest<Message = #request>
301         ) -> std::result::Result<tonic::Response<#response>, tonic::Status> {
302             self.inner.ready().await.map_err(|e| {
303                 tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
304             })?;
305             let codec = #codec_name::default();
306             let path = http::uri::PathAndQuery::from_static(#path);
307             let mut req = request.into_streaming_request();
308             req.extensions_mut().insert(GrpcMethod::new(#service_name, #method_name));
309             self.inner.client_streaming(req, path, codec).await
310         }
311     }
312 }
313 
generate_streaming<T: Service>( service: &T, method: &T::Method, emit_package: bool, proto_path: &str, compile_well_known_types: bool, ) -> TokenStream314 fn generate_streaming<T: Service>(
315     service: &T,
316     method: &T::Method,
317     emit_package: bool,
318     proto_path: &str,
319     compile_well_known_types: bool,
320 ) -> TokenStream {
321     let codec_name = syn::parse_str::<syn::Path>(method.codec_path()).unwrap();
322     let ident = format_ident!("{}", method.name());
323     let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
324     let service_name = format_service_name(service, emit_package);
325     let path = format_method_path(service, method, emit_package);
326     let method_name = method.identifier();
327 
328     quote! {
329         pub async fn #ident(
330             &mut self,
331             request: impl tonic::IntoStreamingRequest<Message = #request>
332         ) -> std::result::Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
333             self.inner.ready().await.map_err(|e| {
334                 tonic::Status::unknown(format!("Service was not ready: {}", e.into()))
335             })?;
336             let codec = #codec_name::default();
337             let path = http::uri::PathAndQuery::from_static(#path);
338             let mut req = request.into_streaming_request();
339             req.extensions_mut().insert(GrpcMethod::new(#service_name,#method_name));
340             self.inner.streaming(req, path, codec).await
341         }
342     }
343 }
344