tonic_build/
client.rs

1use std::collections::HashSet;
2
3use super::{Attributes, Method, Service};
4use crate::{
5    format_method_name, format_method_path, format_service_name, generate_deprecated,
6    generate_doc_comments, naive_snake_case,
7};
8use proc_macro2::TokenStream;
9use quote::{format_ident, quote};
10
11pub(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")]
140fn 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"))]
163fn generate_connect(_service_ident: &syn::Ident, _enabled: bool) -> TokenStream {
164    TokenStream::new()
165}
166
167fn 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
221fn 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
252fn 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
283fn 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
314fn 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}