1#![recursion_limit = "256"]
61#![warn(
62 missing_debug_implementations,
63 missing_docs,
64 rust_2018_idioms,
65 unreachable_pub
66)]
67#![doc(
68 html_logo_url = "https://raw.githubusercontent.com/tokio-rs/website/master/public/img/icons/tonic.svg"
69)]
70#![deny(rustdoc::broken_intra_doc_links)]
71#![doc(html_root_url = "https://docs.rs/tonic-build/0.6.0")]
72#![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")]
73#![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))]
74#![cfg_attr(docsrs, feature(doc_cfg))]
75
76use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream};
77use quote::TokenStreamExt;
78
79#[cfg(feature = "prost")]
81#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
82mod prost;
83
84#[cfg(feature = "prost")]
85#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
86pub use prost::{compile_protos, configure, Builder};
87
88#[cfg(feature = "rustfmt")]
89#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))]
90use std::io::{self, Write};
91#[cfg(feature = "rustfmt")]
92#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))]
93use std::process::{exit, Command};
94
95pub mod client;
97pub mod server;
99
100pub trait Service {
107 const CODEC_PATH: &'static str;
109
110 type Comment: AsRef<str>;
112
113 type Method: Method;
115
116 fn name(&self) -> &str;
118 fn package(&self) -> &str;
120 fn identifier(&self) -> &str;
122 fn methods(&self) -> &[Self::Method];
124 fn comment(&self) -> &[Self::Comment];
126}
127
128pub trait Method {
135 const CODEC_PATH: &'static str;
137 type Comment: AsRef<str>;
139
140 fn name(&self) -> &str;
142 fn identifier(&self) -> &str;
144 fn client_streaming(&self) -> bool;
146 fn server_streaming(&self) -> bool;
148 fn comment(&self) -> &[Self::Comment];
150 fn request_response_name(
152 &self,
153 proto_path: &str,
154 compile_well_known_types: bool,
155 ) -> (TokenStream, TokenStream);
156}
157
158#[derive(Debug, Default, Clone)]
160pub struct Attributes {
161 module: Vec<(String, String)>,
163 structure: Vec<(String, String)>,
165}
166
167impl Attributes {
168 fn for_mod(&self, name: &str) -> Vec<syn::Attribute> {
169 generate_attributes(name, &self.module)
170 }
171
172 fn for_struct(&self, name: &str) -> Vec<syn::Attribute> {
173 generate_attributes(name, &self.structure)
174 }
175
176 pub fn push_mod(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
186 self.module.push((pattern.into(), attr.into()));
187 }
188
189 pub fn push_struct(&mut self, pattern: impl Into<String>, attr: impl Into<String>) {
199 self.structure.push((pattern.into(), attr.into()));
200 }
201}
202
203fn generate_attributes<'a>(
205 name: &str,
206 attrs: impl IntoIterator<Item = &'a (String, String)>,
207) -> Vec<syn::Attribute> {
208 attrs
209 .into_iter()
210 .filter(|(matcher, _)| match_name(matcher, name))
211 .flat_map(|(_, attr)| {
212 syn::parse_str::<syn::DeriveInput>(&format!("{}\nstruct fake;", attr))
214 .unwrap()
215 .attrs
216 })
217 .collect::<Vec<_>>()
218}
219
220#[cfg(feature = "rustfmt")]
222#[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))]
223pub fn fmt(out_dir: &str) {
224 let dir = std::fs::read_dir(out_dir).unwrap();
225
226 for entry in dir {
227 let file = entry.unwrap().file_name().into_string().unwrap();
228 if !file.ends_with(".rs") {
229 continue;
230 }
231 let result =
232 Command::new(std::env::var("RUSTFMT").unwrap_or_else(|_| "rustfmt".to_owned()))
233 .arg("--emit")
234 .arg("files")
235 .arg("--edition")
236 .arg("2018")
237 .arg(format!("{}/{}", out_dir, file))
238 .output();
239
240 match result {
241 Err(e) => {
242 eprintln!("error running rustfmt: {:?}", e);
243 exit(1)
244 }
245 Ok(output) => {
246 if !output.status.success() {
247 io::stdout().write_all(&output.stdout).unwrap();
248 io::stderr().write_all(&output.stderr).unwrap();
249 exit(output.status.code().unwrap_or(1))
250 }
251 }
252 }
253 }
254}
255
256fn generate_doc_comment<S: AsRef<str>>(comment: S) -> TokenStream {
258 let mut doc_stream = TokenStream::new();
259
260 doc_stream.append(Ident::new("doc", Span::call_site()));
261 doc_stream.append(Punct::new('=', Spacing::Alone));
262 doc_stream.append(Literal::string(comment.as_ref()));
263
264 let group = Group::new(Delimiter::Bracket, doc_stream);
265
266 let mut stream = TokenStream::new();
267 stream.append(Punct::new('#', Spacing::Alone));
268 stream.append(group);
269 stream
270}
271
272fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream {
274 let mut stream = TokenStream::new();
275
276 for comment in comments {
277 stream.extend(generate_doc_comment(comment));
278 }
279
280 stream
281}
282
283pub(crate) fn match_name(pattern: &str, path: &str) -> bool {
285 if pattern.is_empty() {
286 false
287 } else if pattern == "." || pattern == path {
288 true
289 } else {
290 let pattern_segments = pattern.split('.').collect::<Vec<_>>();
291 let path_segments = path.split('.').collect::<Vec<_>>();
292
293 if &pattern[..1] == "." {
294 if pattern_segments.len() > path_segments.len() {
296 false
297 } else {
298 pattern_segments[..] == path_segments[..pattern_segments.len()]
299 }
300 } else if pattern_segments.len() > path_segments.len() {
302 false
303 } else {
304 pattern_segments[..] == path_segments[path_segments.len() - pattern_segments.len()..]
305 }
306 }
307}
308
309fn naive_snake_case(name: &str) -> String {
310 let mut s = String::new();
311 let mut it = name.chars().peekable();
312
313 while let Some(x) = it.next() {
314 s.push(x.to_ascii_lowercase());
315 if let Some(y) = it.peek() {
316 if y.is_uppercase() {
317 s.push('_');
318 }
319 }
320 }
321
322 s
323}
324
325#[cfg(test)]
326mod tests {
327 use super::*;
328
329 #[test]
330 fn test_match_name() {
331 assert!(match_name(".", ".my.protos"));
332 assert!(match_name(".", ".protos"));
333
334 assert!(match_name(".my", ".my"));
335 assert!(match_name(".my", ".my.protos"));
336 assert!(match_name(".my.protos.Service", ".my.protos.Service"));
337
338 assert!(match_name("Service", ".my.protos.Service"));
339
340 assert!(!match_name(".m", ".my.protos"));
341 assert!(!match_name(".p", ".protos"));
342
343 assert!(!match_name(".my", ".myy"));
344 assert!(!match_name(".protos", ".my.protos"));
345 assert!(!match_name(".Service", ".my.protos.Service"));
346
347 assert!(!match_name("service", ".my.protos.Service"));
348 }
349
350 #[test]
351 fn test_snake_case() {
352 for case in &[
353 ("Service", "service"),
354 ("ThatHasALongName", "that_has_a_long_name"),
355 ("greeter", "greeter"),
356 ("ABCServiceX", "a_b_c_service_x"),
357 ] {
358 assert_eq!(naive_snake_case(case.0), case.1)
359 }
360 }
361}