tonic_build/
lib.rs

1//! `tonic-build` compiles `proto` files via `prost` and generates service stubs
2//! and proto definitions for use with `tonic`.
3//!
4//! # Feature flags
5//!
6//! - `cleanup-markdown`: Enables cleaning up documentation from the generated code. Useful
7//!   when documentation of the generated code fails `cargo test --doc` for example. The
8//!   `prost` feature must be enabled to use this feature.
9//! - `prost`: Enables usage of prost generator (enabled by default).
10//! - `transport`: Enables generation of `connect` method using `tonic::transport::Channel`
11//!   (enabled by default).
12//!
13//! # Required dependencies
14//!
15//! ```toml
16//! [dependencies]
17//! tonic = <tonic-version>
18//! prost = <prost-version>
19//!
20//! [build-dependencies]
21//! tonic-build = <tonic-version>
22//! ```
23//!
24//! # Examples
25//! Simple
26//!
27//! ```rust,no_run
28//! fn main() -> Result<(), Box<dyn std::error::Error>> {
29//!     tonic_build::compile_protos("proto/service.proto")?;
30//!     Ok(())
31//! }
32//! ```
33//!
34//! Configuration
35//!
36//! ```rust,no_run
37//! fn main() -> Result<(), Box<dyn std::error::Error>> {
38//!    tonic_build::configure()
39//!         .build_server(false)
40//!         .compile_protos(
41//!             &["proto/helloworld/helloworld.proto"],
42//!             &["proto/helloworld"],
43//!         )?;
44//!    Ok(())
45//! }
46//!```
47//!
48//! ## NixOS related hints
49//!
50//! On NixOS, it is better to specify the location of `PROTOC` and `PROTOC_INCLUDE` explicitly.
51//!
52//! ```bash
53//! $ export PROTOBUF_LOCATION=$(nix-env -q protobuf --out-path --no-name)
54//! $ export PROTOC=$PROTOBUF_LOCATION/bin/protoc
55//! $ export PROTOC_INCLUDE=$PROTOBUF_LOCATION/include
56//! $ cargo build
57//! ```
58//!
59//! The reason being that if `prost_build::compile_protos` fails to generate the resultant package,
60//! the failure is not obvious until the `include!(concat!(env!("OUT_DIR"), "/resultant.rs"));`
61//! fails with `No such file or directory` error.
62
63#![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/// Prost generator
75#[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
87/// Service code generation for client
88pub mod client;
89/// Service code generation for Server
90pub mod server;
91
92mod code_gen;
93pub use code_gen::CodeGenBuilder;
94
95mod compile_settings;
96
97/// Service generation trait.
98///
99/// This trait can be implemented and consumed
100/// by `client::generate` and `server::generate`
101/// to allow any codegen module to generate service
102/// abstractions.
103pub trait Service {
104    /// Comment type.
105    type Comment: AsRef<str>;
106
107    /// Method type.
108    type Method: Method;
109
110    /// Name of service.
111    fn name(&self) -> &str;
112    /// Package name of service.
113    fn package(&self) -> &str;
114    /// Identifier used to generate type name.
115    fn identifier(&self) -> &str;
116    /// Methods provided by service.
117    fn methods(&self) -> &[Self::Method];
118    /// Get comments about this item.
119    fn comment(&self) -> &[Self::Comment];
120}
121
122/// Method generation trait.
123///
124/// Each service contains a set of generic
125/// `Methods`'s that will be used by codegen
126/// to generate abstraction implementations for
127/// the provided methods.
128pub trait Method {
129    /// Comment type.
130    type Comment: AsRef<str>;
131
132    /// Name of method.
133    fn name(&self) -> &str;
134    /// Identifier used to generate type name.
135    fn identifier(&self) -> &str;
136    /// Path to the codec.
137    fn codec_path(&self) -> &str;
138    /// Method is streamed by client.
139    fn client_streaming(&self) -> bool;
140    /// Method is streamed by server.
141    fn server_streaming(&self) -> bool;
142    /// Get comments about this item.
143    fn comment(&self) -> &[Self::Comment];
144    /// Method is deprecated.
145    fn deprecated(&self) -> bool {
146        false
147    }
148    /// Type name of request and response.
149    fn request_response_name(
150        &self,
151        proto_path: &str,
152        compile_well_known_types: bool,
153    ) -> (TokenStream, TokenStream);
154}
155
156/// Attributes that will be added to `mod` and `struct` items.
157#[derive(Debug, Default, Clone)]
158pub struct Attributes {
159    /// `mod` attributes.
160    module: Vec<(String, String)>,
161    /// `struct` attributes.
162    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    /// Add an attribute that will be added to `mod` items matching the given pattern.
175    ///
176    /// # Examples
177    ///
178    /// ```
179    /// # use tonic_build::*;
180    /// let mut attributes = Attributes::default();
181    /// attributes.push_mod("my.proto.package", r#"#[cfg(feature = "server")]"#);
182    /// ```
183    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    /// Add an attribute that will be added to `struct` items matching the given pattern.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// # use tonic_build::*;
193    /// let mut attributes = Attributes::default();
194    /// attributes.push_struct("EchoService", "#[derive(PartialEq)]");
195    /// ```
196    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
227// Generates attributes given a list of (`pattern`, `attribute`) pairs. If `pattern` matches `name`, `attribute` will be included.
228fn 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            // attributes cannot be parsed directly, so we pretend they're on a struct
237            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
257// Generate a singular line of a doc comment
258fn 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
281// Generate a larger doc comment composed of many lines of doc comments
282fn 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
292// Checks whether a path pattern matches a given path.
293pub(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            // prefix match
304            if pattern_segments.len() > path_segments.len() {
305                false
306            } else {
307                pattern_segments[..] == path_segments[..pattern_segments.len()]
308            }
309        // suffix match
310        } 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}