informalsystems_tonic_build/
server.rs

1use super::{Attributes, Method, Service};
2use crate::{generate_doc_comment, generate_doc_comments, naive_snake_case};
3use proc_macro2::{Span, TokenStream};
4use quote::quote;
5use syn::{Ident, Lit, LitStr};
6
7/// Generate service for Server.
8///
9/// This takes some `Service` and will generate a `TokenStream` that contains
10/// a public module containing the server service and handler trait.
11pub fn generate<T: Service>(
12    service: &T,
13    emit_package: bool,
14    proto_path: &str,
15    compile_well_known_types: bool,
16    attributes: &Attributes,
17) -> TokenStream {
18    let methods = generate_methods(service, proto_path, compile_well_known_types);
19
20    let server_service = quote::format_ident!("{}Server", service.name());
21    let server_trait = quote::format_ident!("{}", service.name());
22    let server_mod = quote::format_ident!("{}_server", naive_snake_case(&service.name()));
23    let generated_trait = generate_trait(
24        service,
25        proto_path,
26        compile_well_known_types,
27        server_trait.clone(),
28    );
29    let service_doc = generate_doc_comments(service.comment());
30    let package = if emit_package { service.package() } else { "" };
31    // Transport based implementations
32    let path = format!(
33        "{}{}{}",
34        package,
35        if package.is_empty() { "" } else { "." },
36        service.identifier()
37    );
38    let transport = generate_transport(&server_service, &server_trait, &path);
39    let mod_attributes = attributes.for_mod(package);
40    let struct_attributes = attributes.for_struct(&path);
41
42    let compression_enabled = cfg!(feature = "compression");
43
44    let compression_config_ty = if compression_enabled {
45        quote! { EnabledCompressionEncodings }
46    } else {
47        quote! { () }
48    };
49
50    let configure_compression_methods = if compression_enabled {
51        quote! {
52            /// Enable decompressing requests with `gzip`.
53            pub fn accept_gzip(mut self) -> Self {
54                self.accept_compression_encodings.enable_gzip();
55                self
56            }
57
58            /// Compress responses with `gzip`, if the client supports it.
59            pub fn send_gzip(mut self) -> Self {
60                self.send_compression_encodings.enable_gzip();
61                self
62            }
63        }
64    } else {
65        quote! {}
66    };
67
68    quote! {
69        /// Generated server implementations.
70        #(#mod_attributes)*
71        pub mod #server_mod {
72            #![allow(
73                unused_variables,
74                dead_code,
75                missing_docs,
76                // will trigger if compression is disabled
77                clippy::let_unit_value,
78            )]
79            use tonic::codegen::*;
80
81            #generated_trait
82
83            #service_doc
84            #(#struct_attributes)*
85            #[derive(Debug)]
86            pub struct #server_service<T: #server_trait> {
87                inner: _Inner<T>,
88                accept_compression_encodings: #compression_config_ty,
89                send_compression_encodings: #compression_config_ty,
90            }
91
92            struct _Inner<T>(Arc<T>);
93
94            impl<T: #server_trait> #server_service<T> {
95                pub fn new(inner: T) -> Self {
96                    let inner = Arc::new(inner);
97                    let inner = _Inner(inner);
98                    Self {
99                        inner,
100                        accept_compression_encodings: Default::default(),
101                        send_compression_encodings: Default::default(),
102                    }
103                }
104
105                pub fn with_interceptor<F>(inner: T, interceptor: F) -> InterceptedService<Self, F>
106                where
107                    F: tonic::service::Interceptor,
108                {
109                    InterceptedService::new(Self::new(inner), interceptor)
110                }
111
112                #configure_compression_methods
113            }
114
115            impl<T, B> tonic::codegen::Service<http::Request<B>> for #server_service<T>
116                where
117                    T: #server_trait,
118                    B: Body + Send + Sync + 'static,
119                    B::Error: Into<StdError> + Send + 'static,
120            {
121                type Response = http::Response<tonic::body::BoxBody>;
122                type Error = Never;
123                type Future = BoxFuture<Self::Response, Self::Error>;
124
125                fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
126                    Poll::Ready(Ok(()))
127                }
128
129                fn call(&mut self, req: http::Request<B>) -> Self::Future {
130                    let inner = self.inner.clone();
131
132                    match req.uri().path() {
133                        #methods
134
135                        _ => Box::pin(async move {
136                            Ok(http::Response::builder()
137                               .status(200)
138                               .header("grpc-status", "12")
139                               .header("content-type", "application/grpc")
140                               .body(empty_body())
141                               .unwrap())
142                        }),
143                    }
144                }
145            }
146
147            impl<T: #server_trait> Clone for #server_service<T> {
148                fn clone(&self) -> Self {
149                    let inner = self.inner.clone();
150                    Self {
151                        inner,
152                        accept_compression_encodings: self.accept_compression_encodings,
153                        send_compression_encodings: self.send_compression_encodings,
154                    }
155                }
156            }
157
158            impl<T: #server_trait> Clone for _Inner<T> {
159                fn clone(&self) -> Self {
160                    Self(self.0.clone())
161                }
162            }
163
164            impl<T: std::fmt::Debug> std::fmt::Debug for _Inner<T> {
165                fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166                   write!(f, "{:?}", self.0)
167                }
168            }
169
170            #transport
171        }
172    }
173}
174
175fn generate_trait<T: Service>(
176    service: &T,
177    proto_path: &str,
178    compile_well_known_types: bool,
179    server_trait: Ident,
180) -> TokenStream {
181    let methods = generate_trait_methods(service, proto_path, compile_well_known_types);
182    let trait_doc = generate_doc_comment(&format!(
183        "Generated trait containing gRPC methods that should be implemented for use with {}Server.",
184        service.name()
185    ));
186
187    quote! {
188        #trait_doc
189        #[async_trait]
190        pub trait #server_trait : Send + Sync + 'static {
191            #methods
192        }
193    }
194}
195
196fn generate_trait_methods<T: Service>(
197    service: &T,
198    proto_path: &str,
199    compile_well_known_types: bool,
200) -> TokenStream {
201    let mut stream = TokenStream::new();
202
203    for method in service.methods() {
204        let name = quote::format_ident!("{}", method.name());
205
206        let (req_message, res_message) =
207            method.request_response_name(proto_path, compile_well_known_types);
208
209        let method_doc = generate_doc_comments(method.comment());
210
211        let method = match (method.client_streaming(), method.server_streaming()) {
212            (false, false) => {
213                quote! {
214                    #method_doc
215                    async fn #name(&self, request: tonic::Request<#req_message>)
216                        -> Result<tonic::Response<#res_message>, tonic::Status>;
217                }
218            }
219            (true, false) => {
220                quote! {
221                    #method_doc
222                    async fn #name(&self, request: tonic::Request<tonic::Streaming<#req_message>>)
223                        -> Result<tonic::Response<#res_message>, tonic::Status>;
224                }
225            }
226            (false, true) => {
227                let stream = quote::format_ident!("{}Stream", method.identifier());
228                let stream_doc = generate_doc_comment(&format!(
229                    "Server streaming response type for the {} method.",
230                    method.identifier()
231                ));
232
233                quote! {
234                    #stream_doc
235                    type #stream: futures_core::Stream<Item = Result<#res_message, tonic::Status>> + Send + Sync + 'static;
236
237                    #method_doc
238                    async fn #name(&self, request: tonic::Request<#req_message>)
239                        -> Result<tonic::Response<Self::#stream>, tonic::Status>;
240                }
241            }
242            (true, true) => {
243                let stream = quote::format_ident!("{}Stream", method.identifier());
244                let stream_doc = generate_doc_comment(&format!(
245                    "Server streaming response type for the {} method.",
246                    method.identifier()
247                ));
248
249                quote! {
250                    #stream_doc
251                    type #stream: futures_core::Stream<Item = Result<#res_message, tonic::Status>> + Send + Sync + 'static;
252
253                    #method_doc
254                    async fn #name(&self, request: tonic::Request<tonic::Streaming<#req_message>>)
255                        -> Result<tonic::Response<Self::#stream>, tonic::Status>;
256                }
257            }
258        };
259
260        stream.extend(method);
261    }
262
263    stream
264}
265
266#[cfg(feature = "transport")]
267fn generate_transport(
268    server_service: &syn::Ident,
269    server_trait: &syn::Ident,
270    service_name: &str,
271) -> TokenStream {
272    let service_name = syn::LitStr::new(service_name, proc_macro2::Span::call_site());
273
274    quote! {
275        impl<T: #server_trait> tonic::transport::NamedService for #server_service<T> {
276            const NAME: &'static str = #service_name;
277        }
278    }
279}
280
281#[cfg(not(feature = "transport"))]
282fn generate_transport(
283    _server_service: &syn::Ident,
284    _server_trait: &syn::Ident,
285    _service_name: &str,
286) -> TokenStream {
287    TokenStream::new()
288}
289
290fn generate_methods<T: Service>(
291    service: &T,
292    proto_path: &str,
293    compile_well_known_types: bool,
294) -> TokenStream {
295    let mut stream = TokenStream::new();
296
297    for method in service.methods() {
298        let path = format!(
299            "/{}{}{}/{}",
300            service.package(),
301            if service.package().is_empty() {
302                ""
303            } else {
304                "."
305            },
306            service.identifier(),
307            method.identifier()
308        );
309        let method_path = Lit::Str(LitStr::new(&path, Span::call_site()));
310        let ident = quote::format_ident!("{}", method.name());
311        let server_trait = quote::format_ident!("{}", service.name());
312
313        let method_stream = match (method.client_streaming(), method.server_streaming()) {
314            (false, false) => generate_unary(
315                method,
316                proto_path,
317                compile_well_known_types,
318                ident,
319                server_trait,
320            ),
321
322            (false, true) => generate_server_streaming(
323                method,
324                proto_path,
325                compile_well_known_types,
326                ident.clone(),
327                server_trait,
328            ),
329            (true, false) => generate_client_streaming(
330                method,
331                proto_path,
332                compile_well_known_types,
333                ident.clone(),
334                server_trait,
335            ),
336
337            (true, true) => generate_streaming(
338                method,
339                proto_path,
340                compile_well_known_types,
341                ident.clone(),
342                server_trait,
343            ),
344        };
345
346        let method = quote! {
347            #method_path => {
348                #method_stream
349            }
350        };
351        stream.extend(method);
352    }
353
354    stream
355}
356
357fn generate_unary<T: Method>(
358    method: &T,
359    proto_path: &str,
360    compile_well_known_types: bool,
361    method_ident: Ident,
362    server_trait: Ident,
363) -> TokenStream {
364    let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
365
366    let service_ident = quote::format_ident!("{}Svc", method.identifier());
367
368    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
369
370    quote! {
371        #[allow(non_camel_case_types)]
372        struct #service_ident<T: #server_trait >(pub Arc<T>);
373
374        impl<T: #server_trait> tonic::server::UnaryService<#request> for #service_ident<T> {
375            type Response = #response;
376            type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
377
378            fn call(&mut self, request: tonic::Request<#request>) -> Self::Future {
379                let inner = self.0.clone();
380                let fut = async move {
381                    (*inner).#method_ident(request).await
382                };
383                Box::pin(fut)
384            }
385        }
386
387        let accept_compression_encodings = self.accept_compression_encodings;
388        let send_compression_encodings = self.send_compression_encodings;
389        let inner = self.inner.clone();
390        let fut = async move {
391            let inner = inner.0;
392            let method = #service_ident(inner);
393            let codec = #codec_name::default();
394
395            let mut grpc = tonic::server::Grpc::new(codec)
396                .apply_compression_config(accept_compression_encodings, send_compression_encodings);
397
398            let res = grpc.unary(method, req).await;
399            Ok(res)
400        };
401
402        Box::pin(fut)
403    }
404}
405
406fn generate_server_streaming<T: Method>(
407    method: &T,
408    proto_path: &str,
409    compile_well_known_types: bool,
410    method_ident: Ident,
411    server_trait: Ident,
412) -> TokenStream {
413    let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
414
415    let service_ident = quote::format_ident!("{}Svc", method.identifier());
416
417    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
418
419    let response_stream = quote::format_ident!("{}Stream", method.identifier());
420
421    quote! {
422        #[allow(non_camel_case_types)]
423        struct #service_ident<T: #server_trait >(pub Arc<T>);
424
425        impl<T: #server_trait> tonic::server::ServerStreamingService<#request> for #service_ident<T> {
426            type Response = #response;
427            type ResponseStream = T::#response_stream;
428            type Future = BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
429
430            fn call(&mut self, request: tonic::Request<#request>) -> Self::Future {
431                let inner = self.0.clone();
432                let fut = async move {
433                    (*inner).#method_ident(request).await
434                };
435                Box::pin(fut)
436            }
437        }
438
439        let accept_compression_encodings = self.accept_compression_encodings;
440        let send_compression_encodings = self.send_compression_encodings;
441        let inner = self.inner.clone();
442        let fut = async move {
443            let inner = inner.0;
444            let method = #service_ident(inner);
445            let codec = #codec_name::default();
446
447            let mut grpc = tonic::server::Grpc::new(codec)
448                .apply_compression_config(accept_compression_encodings, send_compression_encodings);
449
450            let res = grpc.server_streaming(method, req).await;
451            Ok(res)
452        };
453
454        Box::pin(fut)
455    }
456}
457
458fn generate_client_streaming<T: Method>(
459    method: &T,
460    proto_path: &str,
461    compile_well_known_types: bool,
462    method_ident: Ident,
463    server_trait: Ident,
464) -> TokenStream {
465    let service_ident = quote::format_ident!("{}Svc", method.identifier());
466
467    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
468    let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
469
470    quote! {
471        #[allow(non_camel_case_types)]
472        struct #service_ident<T: #server_trait >(pub Arc<T>);
473
474        impl<T: #server_trait> tonic::server::ClientStreamingService<#request> for #service_ident<T>
475        {
476            type Response = #response;
477            type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
478
479            fn call(&mut self, request: tonic::Request<tonic::Streaming<#request>>) -> Self::Future {
480                let inner = self.0.clone();
481                let fut = async move {
482                    (*inner).#method_ident(request).await
483
484                };
485                Box::pin(fut)
486            }
487        }
488
489        let accept_compression_encodings = self.accept_compression_encodings;
490        let send_compression_encodings = self.send_compression_encodings;
491        let inner = self.inner.clone();
492        let fut = async move {
493            let inner = inner.0;
494            let method = #service_ident(inner);
495            let codec = #codec_name::default();
496
497            let mut grpc = tonic::server::Grpc::new(codec)
498                .apply_compression_config(accept_compression_encodings, send_compression_encodings);
499
500            let res = grpc.client_streaming(method, req).await;
501            Ok(res)
502        };
503
504        Box::pin(fut)
505    }
506}
507
508fn generate_streaming<T: Method>(
509    method: &T,
510    proto_path: &str,
511    compile_well_known_types: bool,
512    method_ident: Ident,
513    server_trait: Ident,
514) -> TokenStream {
515    let codec_name = syn::parse_str::<syn::Path>(T::CODEC_PATH).unwrap();
516
517    let service_ident = quote::format_ident!("{}Svc", method.identifier());
518
519    let (request, response) = method.request_response_name(proto_path, compile_well_known_types);
520
521    let response_stream = quote::format_ident!("{}Stream", method.identifier());
522
523    quote! {
524        #[allow(non_camel_case_types)]
525        struct #service_ident<T: #server_trait>(pub Arc<T>);
526
527        impl<T: #server_trait> tonic::server::StreamingService<#request> for #service_ident<T>
528        {
529            type Response = #response;
530            type ResponseStream = T::#response_stream;
531            type Future = BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
532
533            fn call(&mut self, request: tonic::Request<tonic::Streaming<#request>>) -> Self::Future {
534                let inner = self.0.clone();
535                let fut = async move {
536                    (*inner).#method_ident(request).await
537                };
538                Box::pin(fut)
539            }
540        }
541
542        let accept_compression_encodings = self.accept_compression_encodings;
543        let send_compression_encodings = self.send_compression_encodings;
544        let inner = self.inner.clone();
545        let fut = async move {
546            let inner = inner.0;
547            let method = #service_ident(inner);
548            let codec = #codec_name::default();
549
550            let mut grpc = tonic::server::Grpc::new(codec)
551                .apply_compression_config(accept_compression_encodings, send_compression_encodings);
552
553            let res = grpc.streaming(method, req).await;
554            Ok(res)
555        };
556
557        Box::pin(fut)
558    }
559}