1use super::{Attributes, Method, Service};
2use crate::{generate_doc_comments, naive_snake_case};
3use proc_macro2::TokenStream;
4use quote::{format_ident, quote};
5
6pub fn generate<T: Service>(
11 service: &T,
12 emit_package: bool,
13 proto_path: &str,
14 compile_well_known_types: bool,
15 attributes: &Attributes,
16) -> TokenStream {
17 let service_ident = quote::format_ident!("{}Client", service.name());
18 let client_mod = quote::format_ident!("{}_client", naive_snake_case(&service.name()));
19 let methods = generate_methods(service, emit_package, proto_path, compile_well_known_types);
20
21 let connect = generate_connect(&service_ident);
22 let service_doc = generate_doc_comments(service.comment());
23
24 let package = if emit_package { service.package() } else { "" };
25 let path = format!(
26 "{}{}{}",
27 package,
28 if package.is_empty() { "" } else { "." },
29 service.identifier()
30 );
31
32 let mod_attributes = attributes.for_mod(package);
33 let struct_attributes = attributes.for_struct(&path);
34
35 quote! {
36 #(#mod_attributes)*
38 pub mod #client_mod {
39 #![allow(
40 unused_variables,
41 dead_code,
42 missing_docs,
43 clippy::let_unit_value,
45 )]
46 use tonic::codegen::*;
47
48 #service_doc
49 #(#struct_attributes)*
50 #[derive(Debug, Clone)]
51 pub struct #service_ident<T> {
52 inner: tonic::client::Grpc<T>,
53 }
54
55 #connect
56
57 impl<T> #service_ident<T>
58 where
59 T: tonic::client::GrpcService<tonic::body::BoxBody>,
60 T::ResponseBody: Body + Send + 'static,
61 T::Error: Into<StdError>,
62 <T::ResponseBody as Body>::Error: Into<StdError> + Send,
63 {
64 pub fn new(inner: T) -> Self {
65 let inner = tonic::client::Grpc::new(inner);
66 Self { inner }
67 }
68
69 pub fn with_interceptor<F>(inner: T, interceptor: F) -> #service_ident<InterceptedService<T, F>>
70 where
71 F: tonic::service::Interceptor,
72 T: tonic::codegen::Service<
73 http::Request<tonic::body::BoxBody>,
74 Response = http::Response<<T as tonic::client::GrpcService<tonic::body::BoxBody>>::ResponseBody>
75 >,
76 <T as tonic::codegen::Service<http::Request<tonic::body::BoxBody>>>::Error: Into<StdError> + Send + Sync,
77 {
78 #service_ident::new(InterceptedService::new(inner, interceptor))
79 }
80
81 pub fn send_gzip(mut self) -> Self {
86 self.inner = self.inner.send_gzip();
87 self
88 }
89
90 pub fn accept_gzip(mut self) -> Self {
92 self.inner = self.inner.accept_gzip();
93 self
94 }
95
96 #methods
97 }
98 }
99 }
100}
101
102#[cfg(feature = "transport")]
103fn generate_connect(service_ident: &syn::Ident) -> TokenStream {
104 quote! {
105 impl #service_ident<tonic::transport::Channel> {
106 pub async fn connect<D>(dst: D) -> Result<Self, tonic::transport::Error>
108 where
109 D: std::convert::TryInto<tonic::transport::Endpoint>,
110 D::Error: Into<StdError>,
111 {
112 let conn = tonic::transport::Endpoint::new(dst)?.connect().await?;
113 Ok(Self::new(conn))
114 }
115 }
116 }
117}
118
119#[cfg(not(feature = "transport"))]
120fn generate_connect(_service_ident: &syn::Ident) -> TokenStream {
121 TokenStream::new()
122}
123
124fn generate_methods<T: Service>(
125 service: &T,
126 emit_package: bool,
127 proto_path: &str,
128 compile_well_known_types: bool,
129) -> TokenStream {
130 let mut stream = TokenStream::new();
131 let package = if emit_package { service.package() } else { "" };
132
133 for method in service.methods() {
134 let path = format!(
135 "/{}{}{}/{}",
136 package,
137 if package.is_empty() { "" } else { "." },
138 service.identifier(),
139 method.identifier()
140 );
141
142 stream.extend(generate_doc_comments(method.comment()));
143
144 let method = match (method.client_streaming(), method.server_streaming()) {
145 (false, false) => generate_unary(method, proto_path, compile_well_known_types, path),
146 (false, true) => {
147 generate_server_streaming(method, proto_path, compile_well_known_types, path)
148 }
149 (true, false) => {
150 generate_client_streaming(method, proto_path, compile_well_known_types, path)
151 }
152 (true, true) => generate_streaming(method, proto_path, compile_well_known_types, path),
153 };
154
155 stream.extend(method);
156 }
157
158 stream
159}
160
161fn generate_unary<T: Method>(
162 method: &T,
163 proto_path: &str,
164 compile_well_known_types: bool,
165 path: String,
166) -> TokenStream {
167 let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
168 let ident = format_ident!("{}", method.name());
169 let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
170
171 quote! {
172 pub async fn #ident(
173 &mut self,
174 request: impl tonic::IntoRequest<#request>,
175 ) -> Result<tonic::Response<#response>, tonic::Status> {
176 self.inner.ready().await.map_err(|e| {
177 tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
178 })?;
179 let codec = #codec_name::default();
180 let path = http::uri::PathAndQuery::from_static(#path);
181 self.inner.unary(request.into_request(), path, codec).await
182 }
183 }
184}
185
186fn generate_server_streaming<T: Method>(
187 method: &T,
188 proto_path: &str,
189 compile_well_known_types: bool,
190 path: String,
191) -> TokenStream {
192 let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
193 let ident = format_ident!("{}", method.name());
194
195 let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
196
197 quote! {
198 pub async fn #ident(
199 &mut self,
200 request: impl tonic::IntoRequest<#request>,
201 ) -> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
202 self.inner.ready().await.map_err(|e| {
203 tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
204 })?;
205 let codec = #codec_name::default();
206 let path = http::uri::PathAndQuery::from_static(#path);
207 self.inner.server_streaming(request.into_request(), path, codec).await
208 }
209 }
210}
211
212fn generate_client_streaming<T: Method>(
213 method: &T,
214 proto_path: &str,
215 compile_well_known_types: bool,
216 path: String,
217) -> TokenStream {
218 let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
219 let ident = format_ident!("{}", method.name());
220
221 let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
222
223 quote! {
224 pub async fn #ident(
225 &mut self,
226 request: impl tonic::IntoStreamingRequest<Message = #request>
227 ) -> Result<tonic::Response<#response>, tonic::Status> {
228 self.inner.ready().await.map_err(|e| {
229 tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
230 })?;
231 let codec = #codec_name::default();
232 let path = http::uri::PathAndQuery::from_static(#path);
233 self.inner.client_streaming(request.into_streaming_request(), path, codec).await
234 }
235 }
236}
237
238fn generate_streaming<T: Method>(
239 method: &T,
240 proto_path: &str,
241 compile_well_known_types: bool,
242 path: String,
243) -> TokenStream {
244 let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
245 let ident = format_ident!("{}", method.name());
246
247 let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
248
249 quote! {
250 pub async fn #ident(
251 &mut self,
252 request: impl tonic::IntoStreamingRequest<Message = #request>
253 ) -> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
254 self.inner.ready().await.map_err(|e| {
255 tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
256 })?;
257 let codec = #codec_name::default();
258 let path = http::uri::PathAndQuery::from_static(#path);
259 self.inner.streaming(request.into_streaming_request(), path, codec).await
260 }
261 }
262}