1#![allow(dead_code)]
2pub use protobuf_codegen::{
32 Customize as ProtobufCustomize, CustomizeCallback as ProtobufCustomizeCallback,
33};
34use std::collections::HashMap;
35use std::error::Error;
36use std::fmt;
37use std::fs;
38use std::io;
39use std::io::Read;
40use std::path::Path;
41use std::path::PathBuf;
42pub use ttrpc_compiler::Customize;
43
44mod convert;
45mod model;
46mod parser;
47mod str_lit;
48
49#[derive(Debug, Default)]
51pub struct Codegen {
52 out_dir: Option<PathBuf>,
54 includes: Vec<PathBuf>,
56 inputs: Vec<PathBuf>,
58 rust_protobuf: bool,
60 rust_protobuf_codegen: protobuf_codegen::Codegen,
62 customize: Customize,
64}
65
66impl Codegen {
67 pub fn new() -> Self {
69 Self::default()
70 }
71
72 pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
74 self.out_dir = Some(out_dir.as_ref().to_owned());
75 self
76 }
77
78 pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
80 self.includes.push(include.as_ref().to_owned());
81 self
82 }
83
84 pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
86 for include in includes {
87 self.include(include);
88 }
89 self
90 }
91
92 pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
94 self.inputs.push(input.as_ref().to_owned());
95 self
96 }
97
98 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
100 for input in inputs {
101 self.input(input);
102 }
103 self
104 }
105
106 pub fn rust_protobuf(&mut self) -> &mut Self {
108 self.rust_protobuf = true;
109 self
110 }
111
112 pub fn rust_protobuf_customize(&mut self, customize: ProtobufCustomize) -> &mut Self {
114 self.rust_protobuf_codegen.customize(customize);
115 self
116 }
117
118 pub fn rust_protobuf_customize_callback(
120 &mut self,
121 customize: impl ProtobufCustomizeCallback,
122 ) -> &mut Self {
123 self.rust_protobuf_codegen.customize_callback(customize);
124 self
125 }
126
127 pub fn customize(&mut self, customize: Customize) -> &mut Self {
129 self.customize = customize;
130 self
131 }
132
133 pub fn run(&mut self) -> io::Result<()> {
136 let includes: Vec<&Path> = self.includes.iter().map(|p| p.as_path()).collect();
137 let inputs: Vec<&Path> = self.inputs.iter().map(|p| p.as_path()).collect();
138 let p = parse_and_typecheck(&includes, &inputs)?;
139 let dst_path = self.out_dir.clone().unwrap_or_else(|| {
141 std::env::var("OUT_DIR").map_or_else(
143 |_| std::env::current_dir().unwrap_or_default(),
144 PathBuf::from,
145 )
146 });
147
148 if self.rust_protobuf {
149 self.rust_protobuf_codegen
150 .pure()
151 .out_dir(&dst_path)
152 .inputs(&self.inputs)
153 .includes(&self.includes)
154 .run()
155 .expect("Gen rust protobuf failed.");
156 }
157
158 ttrpc_compiler::codegen::gen_and_write(
159 p.file_descriptors.as_slice(),
160 &p.relative_paths,
161 &dst_path,
162 &self.customize,
163 )
164 }
165}
166
167pub(crate) fn relative_path_to_protobuf_path(path: &Path) -> String {
170 assert!(path.is_relative());
171 let path = path.to_str().expect("not a valid UTF-8 name");
172 if cfg!(windows) {
173 path.replace('\\', "/")
174 } else {
175 path.to_owned()
176 }
177}
178
179#[derive(Clone)]
180struct FileDescriptorPair {
181 parsed: model::FileDescriptor,
182 descriptor: protobuf::descriptor::FileDescriptorProto,
183}
184
185#[derive(Debug)]
186enum CodegenError {
187 ParserErrorWithLocation(parser::ParserErrorWithLocation),
188 ConvertError(convert::ConvertError),
189}
190
191impl From<parser::ParserErrorWithLocation> for CodegenError {
192 fn from(e: parser::ParserErrorWithLocation) -> Self {
193 CodegenError::ParserErrorWithLocation(e)
194 }
195}
196
197impl From<convert::ConvertError> for CodegenError {
198 fn from(e: convert::ConvertError) -> Self {
199 CodegenError::ConvertError(e)
200 }
201}
202
203#[derive(Debug)]
204struct WithFileError {
205 file: String,
206 error: CodegenError,
207}
208
209impl fmt::Display for WithFileError {
210 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211 write!(
212 f,
213 "WithFileError(file: {:?}, error: {:?})",
214 &self.file, &self.error
215 )
216 }
217}
218
219impl Error for WithFileError {
220 fn description(&self) -> &str {
221 "WithFileError"
222 }
223}
224
225struct Run<'a> {
226 parsed_files: HashMap<String, FileDescriptorPair>,
227 includes: &'a [&'a Path],
228}
229
230impl<'a> Run<'a> {
231 fn get_file_and_all_deps_already_parsed(
232 &self,
233 protobuf_path: &str,
234 result: &mut HashMap<String, FileDescriptorPair>,
235 ) {
236 if result.contains_key(protobuf_path) {
237 return;
238 }
239
240 let pair = self
241 .parsed_files
242 .get(protobuf_path)
243 .expect("must be already parsed");
244 result.insert(protobuf_path.to_owned(), pair.clone());
245
246 self.get_all_deps_already_parsed(&pair.parsed, result);
247 }
248
249 fn get_all_deps_already_parsed(
250 &self,
251 parsed: &model::FileDescriptor,
252 result: &mut HashMap<String, FileDescriptorPair>,
253 ) {
254 for import in &parsed.import_paths {
255 self.get_file_and_all_deps_already_parsed(import, result);
256 }
257 }
258
259 fn add_file(&mut self, protobuf_path: &str, fs_path: &Path) -> io::Result<()> {
260 if self.parsed_files.contains_key(protobuf_path) {
261 return Ok(());
262 }
263
264 let mut content = String::new();
265 fs::File::open(fs_path)?.read_to_string(&mut content)?;
266
267 let parsed = model::FileDescriptor::parse(content).map_err(|e| {
268 io::Error::new(
269 io::ErrorKind::Other,
270 WithFileError {
271 file: format!("{}", fs_path.display()),
272 error: e.into(),
273 },
274 )
275 })?;
276
277 for import_path in &parsed.import_paths {
278 self.add_imported_file(import_path)?;
279 }
280
281 let mut this_file_deps = HashMap::new();
282 self.get_all_deps_already_parsed(&parsed, &mut this_file_deps);
283
284 let this_file_deps: Vec<_> = this_file_deps.into_values().map(|v| v.parsed).collect();
285
286 let descriptor =
287 convert::file_descriptor(protobuf_path.to_owned(), &parsed, &this_file_deps).map_err(
288 |e| {
289 io::Error::new(
290 io::ErrorKind::Other,
291 WithFileError {
292 file: format!("{}", fs_path.display()),
293 error: e.into(),
294 },
295 )
296 },
297 )?;
298
299 self.parsed_files.insert(
300 protobuf_path.to_owned(),
301 FileDescriptorPair { parsed, descriptor },
302 );
303
304 Ok(())
305 }
306
307 fn add_imported_file(&mut self, protobuf_path: &str) -> io::Result<()> {
308 for include_dir in self.includes {
309 let fs_path = Path::new(include_dir).join(protobuf_path);
310 if fs_path.exists() {
311 return self.add_file(protobuf_path, &fs_path);
312 }
313 }
314
315 Err(io::Error::new(
316 io::ErrorKind::Other,
317 format!(
318 "protobuf path {:?} is not found in import path {:?}",
319 protobuf_path, self.includes
320 ),
321 ))
322 }
323
324 fn add_fs_file(&mut self, fs_path: &Path) -> io::Result<String> {
325 let relative_path = self
326 .includes
327 .iter()
328 .filter_map(|include_dir| fs_path.strip_prefix(include_dir).ok())
329 .next();
330
331 match relative_path {
332 Some(relative_path) => {
333 let protobuf_path = relative_path_to_protobuf_path(relative_path);
334 self.add_file(&protobuf_path, fs_path)?;
335 Ok(protobuf_path)
336 }
337 None => Err(io::Error::new(
338 io::ErrorKind::Other,
339 format!(
340 "file {:?} must reside in include path {:?}",
341 fs_path, self.includes
342 ),
343 )),
344 }
345 }
346}
347
348#[doc(hidden)]
349pub struct ParsedAndTypechecked {
350 pub relative_paths: Vec<String>,
351 pub file_descriptors: Vec<protobuf::descriptor::FileDescriptorProto>,
352}
353
354#[doc(hidden)]
355pub fn parse_and_typecheck(
356 includes: &[&Path],
357 input: &[&Path],
358) -> io::Result<ParsedAndTypechecked> {
359 let mut run = Run {
360 parsed_files: HashMap::new(),
361 includes,
362 };
363
364 let mut relative_paths = Vec::new();
365
366 for input in input {
367 relative_paths.push(run.add_fs_file(Path::new(input))?);
368 }
369
370 let file_descriptors: Vec<_> = run
371 .parsed_files
372 .into_values()
373 .map(|v| v.descriptor)
374 .collect();
375
376 Ok(ParsedAndTypechecked {
377 relative_paths,
378 file_descriptors,
379 })
380}
381
382#[cfg(test)]
383mod test {
384 use super::*;
385
386 #[cfg(windows)]
387 #[test]
388 fn test_relative_path_to_protobuf_path_windows() {
389 assert_eq!(
390 "foo/bar.proto",
391 relative_path_to_protobuf_path(Path::new("foo\\bar.proto"))
392 );
393 }
394
395 #[test]
396 fn test_relative_path_to_protobuf_path() {
397 assert_eq!(
398 "foo/bar.proto",
399 relative_path_to_protobuf_path(Path::new("foo/bar.proto"))
400 );
401 }
402}