Skip to main content

ttrpc_codegen/
lib.rs

1//! Pure-Rust build-time code generation for ttrpc services.
2//!
3//! `ttrpc-codegen` parses `.proto` files and generates Protocol Buffers messages, typed ttrpc
4//! clients, server traits, and registration helpers. It does not require a `protoc` executable.
5//!
6//! Add this crate under `[build-dependencies]` and invoke [`Codegen`] from `build.rs`.
7//!
8//! # Examples
9//!
10//! ```no_run
11//! use ttrpc_codegen::{Codegen, Customize, ProtobufCustomize};
12//!
13//! # fn main() -> std::io::Result<()> {
14//! Codegen::new()
15//!     .out_dir(std::env::var("OUT_DIR").expect("OUT_DIR is set for build scripts"))
16//!     .input("proto/greeter.proto")
17//!     .include("proto")
18//!     .rust_protobuf()
19//!     .customize(Customize {
20//!         async_all: true,
21//!         gen_mod: true,
22//!         ..Default::default()
23//!     })
24//!     .rust_protobuf_customize(ProtobufCustomize::default().gen_mod_rs(true))
25//!     .run()?;
26//! # Ok(())
27//! # }
28//! ```
29//!
30//! When no output directory is configured, [`Codegen::run`] uses Cargo's `OUT_DIR` environment
31//! variable. Generated module declarations can then be included with:
32//!
33//! ```ignore
34//! include!(concat!(env!("OUT_DIR"), "/mod.rs"));
35//! ```
36
37#![warn(missing_docs)]
38#![warn(rustdoc::broken_intra_doc_links)]
39
40pub use protobuf_codegen::{
41    Customize as ProtobufCustomize, CustomizeCallback as ProtobufCustomizeCallback,
42};
43use std::io;
44use std::path::Path;
45use std::path::PathBuf;
46pub use ttrpc_compiler::Customize;
47
48/// Builder for pure-Rust Protocol Buffers and ttrpc code generation.
49#[derive(Debug, Default)]
50pub struct Codegen {
51    /// Output directory, or `OUT_DIR` when unset.
52    out_dir: Option<PathBuf>,
53    /// Directories searched for imported `.proto` files.
54    includes: Vec<PathBuf>,
55    /// `.proto` files to compile.
56    inputs: Vec<PathBuf>,
57    /// Whether to generate `rust-protobuf` message files alongside service bindings.
58    rust_protobuf: bool,
59    /// Underlying `rust-protobuf` generator configuration.
60    rust_protobuf_codegen: protobuf_codegen::Codegen,
61    /// ttrpc service generator configuration.
62    customize: Customize,
63}
64
65impl Codegen {
66    /// Creates an empty code generation builder.
67    pub fn new() -> Self {
68        Self::default()
69    }
70
71    /// Sets the output directory.
72    pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
73        self.out_dir = Some(out_dir.as_ref().to_owned());
74        self
75    }
76
77    /// Adds a directory searched for imported `.proto` files.
78    ///
79    /// Canonical Google well-known type imports are resolved automatically and
80    /// do not need to be present in an include directory.
81    pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
82        self.includes.push(include.as_ref().to_owned());
83        self
84    }
85
86    /// Adds directories searched for imported `.proto` files.
87    pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
88        for include in includes {
89            self.include(include);
90        }
91        self
92    }
93
94    /// Adds a `.proto` file to compile.
95    pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
96        self.inputs.push(input.as_ref().to_owned());
97        self
98    }
99
100    /// Adds `.proto` files to compile.
101    pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
102        for input in inputs {
103            self.input(input);
104        }
105        self
106    }
107
108    /// Enables generation of `rust-protobuf` message types alongside ttrpc services.
109    pub fn rust_protobuf(&mut self) -> &mut Self {
110        self.rust_protobuf = true;
111        self
112    }
113
114    /// Sets the `rust-protobuf` code generation options.
115    pub fn rust_protobuf_customize(&mut self, customize: ProtobufCustomize) -> &mut Self {
116        self.rust_protobuf_codegen.customize(customize);
117        self
118    }
119
120    /// Sets a callback for per-element `rust-protobuf` customization.
121    pub fn rust_protobuf_customize_callback(
122        &mut self,
123        customize: impl ProtobufCustomizeCallback,
124    ) -> &mut Self {
125        self.rust_protobuf_codegen.customize_callback(customize);
126        self
127    }
128
129    /// Sets ttrpc client and server generation options.
130    pub fn customize(&mut self, customize: Customize) -> &mut Self {
131        self.customize = customize;
132        self
133    }
134
135    /// Parses the configured inputs and writes generated Rust files.
136    ///
137    /// This is equivalent to a `protoc`-based generation step but does not require `protoc` or
138    /// `protoc-gen-rust` in `PATH`.
139    ///
140    /// # Errors
141    ///
142    /// Returns an error if an input cannot be read or parsed, imports or types cannot be resolved,
143    /// or generated service files cannot be written.
144    ///
145    /// # Panics
146    ///
147    /// Panics if `rust_protobuf` generation is enabled and message generation fails.
148    pub fn run(&mut self) -> io::Result<()> {
149        let includes: Vec<&Path> = self.includes.iter().map(|p| p.as_path()).collect();
150        let inputs: Vec<&Path> = self.inputs.iter().map(|p| p.as_path()).collect();
151        let p = parse_and_typecheck(&includes, &inputs)?;
152        // If out_dir is none ,dst_path will be setting in path_dir
153        let dst_path = self.out_dir.clone().unwrap_or_else(|| {
154            // Add default path from env OUT_DIR, if no OUT_DIR env ,that's will be current path
155            std::env::var("OUT_DIR").map_or_else(
156                |_| std::env::current_dir().unwrap_or_default(),
157                PathBuf::from,
158            )
159        });
160
161        if self.rust_protobuf {
162            self.rust_protobuf_codegen
163                .pure()
164                .out_dir(&dst_path)
165                .inputs(&self.inputs)
166                .includes(&self.includes)
167                .run()
168                .expect("Gen rust protobuf failed.");
169        }
170
171        ttrpc_compiler::codegen::gen_and_write(
172            p.file_descriptors.as_slice(),
173            &p.relative_paths,
174            &dst_path,
175            &self.customize,
176        )
177    }
178}
179
180#[doc(hidden)]
181pub struct ParsedAndTypechecked {
182    pub relative_paths: Vec<String>,
183    pub file_descriptors: Vec<protobuf::descriptor::FileDescriptorProto>,
184}
185
186#[doc(hidden)]
187pub fn parse_and_typecheck(
188    includes: &[&Path],
189    input: &[&Path],
190) -> io::Result<ParsedAndTypechecked> {
191    let mut parser = protobuf_parse::Parser::new();
192    parser
193        .pure()
194        .includes(includes.iter().copied())
195        .inputs(input.iter().copied());
196
197    let parsed = parser
198        .parse_and_typecheck()
199        .map_err(|error| io::Error::other(format!("{error:#}")))?;
200
201    Ok(ParsedAndTypechecked {
202        relative_paths: parsed
203            .relative_paths
204            .into_iter()
205            .map(|path| path.to_string())
206            .collect(),
207        file_descriptors: parsed.file_descriptors,
208    })
209}