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}