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