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 #(#mod_attributes)*
47 pub mod #client_mod {
48 #![allow(
49 unused_variables,
50 dead_code,
51 missing_docs,
52 clippy::wildcard_imports,
53 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 #[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 #[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 #[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 #[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 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}