1#![recursion_limit = "256"]
64#![doc(
65 html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
66)]
67#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
68#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
69#![cfg_attr(docsrs, feature(doc_auto_cfg))]
70
71use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
72use quote::TokenStreamExt;
73
74#[cfg(feature = "prost")]
76mod prost;
77#[cfg(feature = "prost")]
78pub use prost_build::Config;
79#[cfg(feature = "prost")]
80pub use prost_types::FileDescriptorSet;
81
82#[cfg(feature = "prost")]
83pub use prost::{compile_fds, compile_protos, configure, Builder};
84
85pub mod manual;
86
87pub mod client;
89pub mod server;
91
92mod code_gen;
93pub use code_gen::CodeGenBuilder;
94
95mod compile_settings;
96
97pub trait Service {
104 type Comment: AsRef<str>;
106
107 type Method: Method;
109
110 fn name(&self) -> &str;
112 fn package(&self) -> &str;
114 fn identifier(&self) -> &str;
116 fn methods(&self) -> &[Self::Method];
118 fn comment(&self) -> &[Self::Comment];
120}
121
122pub trait Method {
129 type Comment: AsRef<str>;
131
132 fn name(&self) -> &str;
134 fn identifier(&self) -> &str;
136 fn codec_path(&self) -> &str;
138 fn client_streaming(&self) -> bool;
140 fn server_streaming(&self) -> bool;
142 fn comment(&self) -> &[Self::Comment];
144 fn deprecated(&self) -> bool {
146 false
147 }
148 fn request_response_name(
150 &self,
151 proto_path: &str,
152 compile_well_known_types: bool,
153 ) -> (TokenStream, TokenStream);
154}
155
156#[derive(Debug, Default, Clone)]
158pub struct Attributes {
159 module: Vec<(String, String)>,
161 structure: Vec<(String, String)>,
163}
164
165impl Attributes {
166 fn for_mod(&self, name: &str) -> Vec<syn::Attribute> {
167 generate_attributes(name, &self.module)
168 }
169
170 fn for_struct(&self, name: &str) -> Vec<syn::Attribute> {
171 generate_attributes(name, &self.structure)
172 }
173
174 pub fn push_mod(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
184 self.module.push((pattern.into(), attr.into()));
185 }
186
187 pub fn push_struct(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
197 self.structure.push((pattern.into(), attr.into()));
198 }
199}
200
201fn format_service_name<T: Service>(service: &T, emit_package: bool) -> String {
202 let package = if emit_package { service.package() } else { "" };
203 format!(
204 "{}{}{}",
205 package,
206 if package.is_empty() { "" } else { "." },
207 service.identifier(),
208 )
209}
210
211fn format_method_path<T: Service>(service: &T, method: &T::Method, emit_package: bool) -> String {
212 format!(
213 "/{}/{}",
214 format_service_name(service, emit_package),
215 method.identifier()
216 )
217}
218
219fn format_method_name<T: Service>(service: &T, method: &T::Method, emit_package: bool) -> String {
220 format!(
221 "{}.{}",
222 format_service_name(service, emit_package),
223 method.identifier()
224 )
225}
226
227fn generate_attributes<'a>(
229 name: &str,
230 attrs: impl IntoIterator<Item = &'a (String, String)>,
231) -> Vec<syn::Attribute> {
232 attrs
233 .into_iter()
234 .filter(|(matcher, _)| match_name(matcher, name))
235 .flat_map(|(_, attr)| {
236 syn::parse_str::<syn::DeriveInput>(&format!("{}\nstruct fake;", attr))
238 .unwrap()
239 .attrs
240 })
241 .collect::<Vec<_>>()
242}
243
244fn generate_deprecated() -> TokenStream {
245 let mut deprecated_stream = TokenStream::new();
246 deprecated_stream.append(Ident::new("deprecated", Span::call_site()));
247
248 let group = Group::new(Delimiter::Bracket, deprecated_stream);
249
250 let mut stream = TokenStream::new();
251 stream.append(Punct::new('#', Spacing::Alone));
252 stream.append(group);
253
254 stream
255}
256
257fn generate_doc_comment<S: AsRef<str>>(comment: S) -> TokenStream {
259 let comment = comment.as_ref();
260
261 let comment = if !comment.starts_with(' ') {
262 format!(" {}", comment)
263 } else {
264 comment.to_string()
265 };
266
267 let mut doc_stream = TokenStream::new();
268
269 doc_stream.append(Ident::new("doc", Span::call_site()));
270 doc_stream.append(Punct::new('=', Spacing::Alone));
271 doc_stream.append(Literal::string(comment.as_ref()));
272
273 let group = Group::new(Delimiter::Bracket, doc_stream);
274
275 let mut stream = TokenStream::new();
276 stream.append(Punct::new('#', Spacing::Alone));
277 stream.append(group);
278 stream
279}
280
281fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
283 let mut stream = TokenStream::new();
284
285 for comment in comments {
286 stream.extend(generate_doc_comment(comment));
287 }
288
289 stream
290}
291
292pub(crate) fn match_name(pattern: &str, path: &str) -> bool {
294 if pattern.is_empty() {
295 false
296 } else if pattern == "." || pattern == path {
297 true
298 } else {
299 let pattern_segments = pattern.split('.').collect::<Vec<_>>();
300 let path_segments = path.split('.').collect::<Vec<_>>();
301
302 if &pattern[..1] == "." {
303 if pattern_segments.len() > path_segments.len() {
305 false
306 } else {
307 pattern_segments[..] == path_segments[..pattern_segments.len()]
308 }
309 } else if pattern_segments.len() > path_segments.len() {
311 false
312 } else {
313 pattern_segments[..] == path_segments[path_segments.len() - pattern_segments.len()..]
314 }
315 }
316}
317
318fn naive_snake_case(name: &str) -> String {
319 let mut s = String::new();
320 let mut it = name.chars().peekable();
321
322 while let Some(x) = it.next() {
323 s.push(x.to_ascii_lowercase());
324 if let Some(y) = it.peek() {
325 if y.is_uppercase() {
326 s.push('_');
327 }
328 }
329 }
330
331 s
332}
333
334#[cfg(test)]
335mod tests {
336 use super::*;
337
338 #[test]
339 fn test_match_name() {
340 assert!(match_name(".", ".my.protos"));
341 assert!(match_name(".", ".protos"));
342
343 assert!(match_name(".my", ".my"));
344 assert!(match_name(".my", ".my.protos"));
345 assert!(match_name(".my.protos.Service", ".my.protos.Service"));
346
347 assert!(match_name("Service", ".my.protos.Service"));
348
349 assert!(!match_name(".m", ".my.protos"));
350 assert!(!match_name(".p", ".protos"));
351
352 assert!(!match_name(".my", ".myy"));
353 assert!(!match_name(".protos", ".my.protos"));
354 assert!(!match_name(".Service", ".my.protos.Service"));
355
356 assert!(!match_name("service", ".my.protos.Service"));
357 }
358
359 #[test]
360 fn test_snake_case() {
361 for case in &[
362 ("Service", "service"),
363 ("ThatHasALongName", "that_has_a_long_name"),
364 ("greeter", "greeter"),
365 ("ABCServiceX", "a_b_c_service_x"),
366 ] {
367 assert_eq!(naive_snake_case(case.0), case.1)
368 }
369 }
370}