1 //! `tonic-build` compiles `proto` files via `prost` and generates service stubs 2 //! and proto definitiones for use with `tonic`. 3 //! 4 //! # Features 5 //! 6 //! - `rustfmt`: This feature enables the use of `rustfmt` to format the output code 7 //! this makes the code readable and the error messages nice. This requires that `rustfmt` 8 //! is installed. This is enabled by default. 9 //! 10 //! # Required dependencies 11 //! 12 //! ```toml 13 //! [dependencies] 14 //! tonic = <tonic-version> 15 //! prost = <prost-version> 16 //! 17 //! [build-dependencies] 18 //! tonic-build = <tonic-version> 19 //! ``` 20 //! 21 //! # Examples 22 //! Simple 23 //! 24 //! ```rust,no_run 25 //! fn main() -> Result<(), Box<dyn std::error::Error>> { 26 //! tonic_build::compile_protos("proto/service.proto")?; 27 //! Ok(()) 28 //! } 29 //! ``` 30 //! 31 //! Configuration 32 //! 33 //! ```rust,no_run 34 //! fn main() -> Result<(), Box<dyn std::error::Error>> { 35 //! tonic_build::configure() 36 //! .build_server(false) 37 //! .compile( 38 //! &["proto/helloworld/helloworld.proto"], 39 //! &["proto/helloworld"], 40 //! )?; 41 //! Ok(()) 42 //! } 43 //! ``` 44 45 #![recursion_limit = "256"] 46 #![warn( 47 missing_debug_implementations, 48 missing_docs, 49 rust_2018_idioms, 50 unreachable_pub 51 )] 52 #![doc( 53 html_logo_url = "https://github.com/hyperium/tonic/raw/master/.github/assets/tonic-docs.png" 54 )] 55 #![doc(html_root_url = "https://docs.rs/tonic-build/0.2.0")] 56 #![doc(issue_tracker_base_url = "https://github.com/hyperium/tonic/issues/")] 57 #![doc(test(no_crate_inject, attr(deny(rust_2018_idioms))))] 58 #![cfg_attr(docsrs, feature(doc_cfg))] 59 60 use proc_macro2::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream}; 61 use quote::TokenStreamExt; 62 63 /// Prost generator 64 #[cfg(feature = "prost")] 65 #[cfg_attr(docsrs, doc(cfg(feature = "prost")))] 66 mod prost; 67 68 #[cfg(feature = "prost")] 69 #[cfg_attr(docsrs, doc(cfg(feature = "prost")))] 70 pub use prost::{compile_protos, configure, Builder}; 71 72 #[cfg(feature = "rustfmt")] 73 #[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] 74 use std::io::{self, Write}; 75 #[cfg(feature = "rustfmt")] 76 #[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] 77 use std::process::{exit, Command}; 78 79 /// Service code generation for client 80 pub mod client; 81 /// Service code generation for Server 82 pub mod server; 83 84 /// Service generation trait. 85 /// 86 /// This trait can be implemented and consumed 87 /// by `client::generate` and `server::generate` 88 /// to allow any codegen module to generate service 89 /// abstractions. 90 pub trait Service { 91 /// Path to the codec. 92 const CODEC_PATH: &'static str; 93 94 /// Comment type. 95 type Comment: AsRef<str>; 96 97 /// Method type. 98 type Method: Method; 99 100 /// Name of service. 101 fn name(&self) -> &str; 102 /// Package name of service. 103 fn package(&self) -> &str; 104 /// Identifier used to generate type name. 105 fn identifier(&self) -> &str; 106 /// Methods provided by service. 107 fn methods(&self) -> &[Self::Method]; 108 /// Get comments about this item. 109 fn comment(&self) -> &[Self::Comment]; 110 } 111 112 /// Method generation trait. 113 /// 114 /// Each service contains a set of generic 115 /// `Methods`'s that will be used by codegen 116 /// to generate abstraction implementations for 117 /// the provided methods. 118 pub trait Method { 119 /// Path to the codec. 120 const CODEC_PATH: &'static str; 121 /// Comment type. 122 type Comment: AsRef<str>; 123 124 /// Name of method. 125 fn name(&self) -> &str; 126 /// Identifier used to generate type name. 127 fn identifier(&self) -> &str; 128 /// Method is streamed by client. 129 fn client_streaming(&self) -> bool; 130 /// Method is streamed by server. 131 fn server_streaming(&self) -> bool; 132 /// Get comments about this item. 133 fn comment(&self) -> &[Self::Comment]; 134 /// Type name of request and response. 135 fn request_response_name(&self, proto_path: &str) -> (TokenStream, TokenStream); 136 } 137 138 /// Format files under the out_dir with rustfmt 139 #[cfg(feature = "rustfmt")] 140 #[cfg_attr(docsrs, doc(cfg(feature = "rustfmt")))] 141 pub fn fmt(out_dir: &str) { 142 let dir = std::fs::read_dir(out_dir).unwrap(); 143 144 for entry in dir { 145 let file = entry.unwrap().file_name().into_string().unwrap(); 146 if !file.ends_with(".rs") { 147 continue; 148 } 149 let result = Command::new("rustfmt") 150 .arg("--emit") 151 .arg("files") 152 .arg("--edition") 153 .arg("2018") 154 .arg(format!("{}/{}", out_dir, file)) 155 .output(); 156 157 match result { 158 Err(e) => { 159 eprintln!("error running rustfmt: {:?}", e); 160 exit(1) 161 } 162 Ok(output) => { 163 if !output.status.success() { 164 io::stderr().write_all(&output.stderr).unwrap(); 165 exit(output.status.code().unwrap_or(1)) 166 } 167 } 168 } 169 } 170 } 171 172 // Generate a singular line of a doc comment 173 fn generate_doc_comment<S: AsRef<str>>(comment: S) -> TokenStream { 174 let mut doc_stream = TokenStream::new(); 175 176 doc_stream.append(Ident::new("doc", Span::call_site())); 177 doc_stream.append(Punct::new('=', Spacing::Alone)); 178 doc_stream.append(Literal::string(comment.as_ref())); 179 180 let group = Group::new(Delimiter::Bracket, doc_stream); 181 182 let mut stream = TokenStream::new(); 183 stream.append(Punct::new('#', Spacing::Alone)); 184 stream.append(group); 185 stream 186 } 187 188 // Generate a larger doc comment composed of many lines of doc comments 189 fn generate_doc_comments<T: AsRef<str>>(comments: &[T]) -> TokenStream { 190 let mut stream = TokenStream::new(); 191 192 for comment in comments { 193 stream.extend(generate_doc_comment(comment)); 194 } 195 196 stream 197 } 198 199 fn naive_snake_case(name: &str) -> String { 200 let mut s = String::new(); 201 let mut it = name.chars().peekable(); 202 203 while let Some(x) = it.next() { 204 s.push(x.to_ascii_lowercase()); 205 if let Some(y) = it.peek() { 206 if y.is_uppercase() { 207 s.push('_'); 208 } 209 } 210 } 211 212 s 213 } 214 215 #[test] 216 fn test_snake_case() { 217 for case in &[ 218 ("Service", "service"), 219 ("ThatHasALongName", "that_has_a_long_name"), 220 ("greeter", "greeter"), 221 ("ABCServiceX", "a_b_c_service_x"), 222 ] { 223 assert_eq!(naive_snake_case(case.0), case.1) 224 } 225 } 226