1 //! API to generate `.rs` files.
2 //!
3 //! This API does not require `protoc` command present in `$PATH`.
4 //!
5 //! ```
6 //! extern crate protoc_rust;
7 //!
8 //! fn main() {
9 //! protobuf_codegen_pure::Codegen::new()
10 //! .out_dir("src/protos")
11 //! .inputs(&["protos/a.proto", "protos/b.proto"]),
12 //! .include("protos")
13 //! .run()
14 //! .expect("Codegen failed.");
15 //! }
16 //! ```
17 //!
18 //! It is advisable that `protobuf-codegen-pure` build-dependecy version be the same as
19 //! `protobuf` dependency.
20 //!
21 //! The alternative is to use `protoc-rust` crate.
22
23 #![deny(missing_docs)]
24 #![deny(intra_doc_link_resolution_failure)]
25
26 extern crate protobuf;
27 extern crate protobuf_codegen;
28
29 mod convert;
30
31 use std::collections::HashMap;
32 use std::error::Error;
33 use std::fmt;
34 use std::fs;
35 use std::io;
36 use std::io::Read;
37 use std::path::Path;
38 use std::path::PathBuf;
39
40 mod model;
41 mod parser;
42 mod str_lit;
43
44 pub use protobuf_codegen::Customize;
45
46 #[cfg(test)]
47 mod test_against_protobuf_protos;
48
49 /// Invoke pure rust codegen. See [crate docs](crate) for example.
50 // TODO: merge with protoc-rust def
51 #[derive(Debug, Default)]
52 pub struct Codegen {
53 /// --lang_out= param
54 out_dir: PathBuf,
55 /// -I args
56 includes: Vec<PathBuf>,
57 /// List of .proto files to compile
58 inputs: Vec<PathBuf>,
59 /// Customize code generation
60 customize: Customize,
61 }
62
63 impl Codegen {
64 /// Fresh new codegen object.
new() -> Self65 pub fn new() -> Self {
66 Self::default()
67 }
68
69 /// Set the output directory for codegen.
out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self70 pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
71 self.out_dir = out_dir.as_ref().to_owned();
72 self
73 }
74
75 /// Add an include directory.
include(&mut self, include: impl AsRef<Path>) -> &mut Self76 pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
77 self.includes.push(include.as_ref().to_owned());
78 self
79 }
80
81 /// Add include directories.
includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self82 pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
83 for include in includes {
84 self.include(include);
85 }
86 self
87 }
88
89 /// Add an input (`.proto` file).
input(&mut self, input: impl AsRef<Path>) -> &mut Self90 pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
91 self.inputs.push(input.as_ref().to_owned());
92 self
93 }
94
95 /// Add inputs (`.proto` files).
inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self96 pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
97 for input in inputs {
98 self.input(input);
99 }
100 self
101 }
102
103 /// Specify generated code [`Customize`] object.
customize(&mut self, customize: Customize) -> &mut Self104 pub fn customize(&mut self, customize: Customize) -> &mut Self {
105 self.customize = customize;
106 self
107 }
108
109 /// Like `protoc --rust_out=...` but without requiring `protoc` or `protoc-gen-rust`
110 /// commands in `$PATH`.
run(&self) -> io::Result<()>111 pub fn run(&self) -> io::Result<()> {
112 let includes: Vec<&Path> = self.includes.iter().map(|p| p.as_path()).collect();
113 let inputs: Vec<&Path> = self.inputs.iter().map(|p| p.as_path()).collect();
114 let p = parse_and_typecheck(&includes, &inputs)?;
115
116 protobuf_codegen::gen_and_write(
117 &p.file_descriptors,
118 &p.relative_paths,
119 &self.out_dir,
120 &self.customize,
121 )
122 }
123 }
124
125 /// Arguments for pure rust codegen invocation.
126 // TODO: merge with protoc-rust def
127 #[derive(Debug, Default)]
128 #[deprecated(since = "2.14", note = "Use Codegen object instead")]
129 pub struct Args<'a> {
130 /// --lang_out= param
131 pub out_dir: &'a str,
132 /// -I args
133 pub includes: &'a [&'a str],
134 /// List of .proto files to compile
135 pub input: &'a [&'a str],
136 /// Customize code generation
137 pub customize: Customize,
138 }
139
140 /// Convert OS path to protobuf path (with slashes)
141 /// Function is `pub(crate)` for test.
relative_path_to_protobuf_path(path: &Path) -> String142 pub(crate) fn relative_path_to_protobuf_path(path: &Path) -> String {
143 assert!(path.is_relative());
144 let path = path.to_str().expect("not a valid UTF-8 name");
145 if cfg!(windows) {
146 path.replace('\\', "/")
147 } else {
148 path.to_owned()
149 }
150 }
151
152 #[derive(Clone)]
153 struct FileDescriptorPair {
154 parsed: model::FileDescriptor,
155 descriptor: protobuf::descriptor::FileDescriptorProto,
156 }
157
158 #[derive(Debug)]
159 enum CodegenError {
160 ParserErrorWithLocation(parser::ParserErrorWithLocation),
161 ConvertError(convert::ConvertError),
162 }
163
164 impl From<parser::ParserErrorWithLocation> for CodegenError {
from(e: parser::ParserErrorWithLocation) -> Self165 fn from(e: parser::ParserErrorWithLocation) -> Self {
166 CodegenError::ParserErrorWithLocation(e)
167 }
168 }
169
170 impl From<convert::ConvertError> for CodegenError {
from(e: convert::ConvertError) -> Self171 fn from(e: convert::ConvertError) -> Self {
172 CodegenError::ConvertError(e)
173 }
174 }
175
176 #[derive(Debug)]
177 struct WithFileError {
178 file: String,
179 error: CodegenError,
180 }
181
182 impl fmt::Display for WithFileError {
fmt(&self, f: &mut fmt::Formatter) -> fmt::Result183 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
184 write!(f, "WithFileError")
185 }
186 }
187
188 impl Error for WithFileError {
description(&self) -> &str189 fn description(&self) -> &str {
190 "WithFileError"
191 }
192 }
193
194 struct Run<'a> {
195 parsed_files: HashMap<String, FileDescriptorPair>,
196 includes: &'a [&'a Path],
197 }
198
199 impl<'a> Run<'a> {
get_file_and_all_deps_already_parsed( &self, protobuf_path: &str, result: &mut HashMap<String, FileDescriptorPair>, )200 fn get_file_and_all_deps_already_parsed(
201 &self,
202 protobuf_path: &str,
203 result: &mut HashMap<String, FileDescriptorPair>,
204 ) {
205 if let Some(_) = result.get(protobuf_path) {
206 return;
207 }
208
209 let pair = self
210 .parsed_files
211 .get(protobuf_path)
212 .expect("must be already parsed");
213 result.insert(protobuf_path.to_owned(), pair.clone());
214
215 self.get_all_deps_already_parsed(&pair.parsed, result);
216 }
217
get_all_deps_already_parsed( &self, parsed: &model::FileDescriptor, result: &mut HashMap<String, FileDescriptorPair>, )218 fn get_all_deps_already_parsed(
219 &self,
220 parsed: &model::FileDescriptor,
221 result: &mut HashMap<String, FileDescriptorPair>,
222 ) {
223 for import in &parsed.import_paths {
224 self.get_file_and_all_deps_already_parsed(import, result);
225 }
226 }
227
add_file(&mut self, protobuf_path: &str, fs_path: &Path) -> io::Result<()>228 fn add_file(&mut self, protobuf_path: &str, fs_path: &Path) -> io::Result<()> {
229 if let Some(_) = self.parsed_files.get(protobuf_path) {
230 return Ok(());
231 }
232
233 let mut content = String::new();
234 fs::File::open(fs_path)?.read_to_string(&mut content)?;
235
236 let parsed = model::FileDescriptor::parse(content).map_err(|e| {
237 io::Error::new(
238 io::ErrorKind::Other,
239 WithFileError {
240 file: format!("{}", fs_path.display()),
241 error: e.into(),
242 },
243 )
244 })?;
245
246 for import_path in &parsed.import_paths {
247 self.add_imported_file(import_path)?;
248 }
249
250 let mut this_file_deps = HashMap::new();
251 self.get_all_deps_already_parsed(&parsed, &mut this_file_deps);
252
253 let this_file_deps: Vec<_> = this_file_deps.into_iter().map(|(_, v)| v.parsed).collect();
254
255 let descriptor =
256 convert::file_descriptor(protobuf_path.to_owned(), &parsed, &this_file_deps).map_err(
257 |e| {
258 io::Error::new(
259 io::ErrorKind::Other,
260 WithFileError {
261 file: format!("{}", fs_path.display()),
262 error: e.into(),
263 },
264 )
265 },
266 )?;
267
268 self.parsed_files.insert(
269 protobuf_path.to_owned(),
270 FileDescriptorPair { parsed, descriptor },
271 );
272
273 Ok(())
274 }
275
add_imported_file(&mut self, protobuf_path: &str) -> io::Result<()>276 fn add_imported_file(&mut self, protobuf_path: &str) -> io::Result<()> {
277 for include_dir in self.includes {
278 let fs_path = Path::new(include_dir).join(protobuf_path);
279 if fs_path.exists() {
280 return self.add_file(protobuf_path, &fs_path);
281 }
282 }
283
284 Err(io::Error::new(
285 io::ErrorKind::Other,
286 format!(
287 "protobuf path {:?} is not found in import path {:?}",
288 protobuf_path, self.includes
289 ),
290 ))
291 }
292
add_fs_file(&mut self, fs_path: &Path) -> io::Result<String>293 fn add_fs_file(&mut self, fs_path: &Path) -> io::Result<String> {
294 let relative_path = self
295 .includes
296 .iter()
297 .filter_map(|include_dir| fs_path.strip_prefix(include_dir).ok())
298 .next();
299
300 match relative_path {
301 Some(relative_path) => {
302 let protobuf_path = relative_path_to_protobuf_path(relative_path);
303 self.add_file(&protobuf_path, fs_path)?;
304 Ok(protobuf_path)
305 }
306 None => Err(io::Error::new(
307 io::ErrorKind::Other,
308 format!(
309 "file {:?} must reside in include path {:?}",
310 fs_path, self.includes
311 ),
312 )),
313 }
314 }
315 }
316
317 #[doc(hidden)]
318 pub struct ParsedAndTypechecked {
319 pub relative_paths: Vec<String>,
320 pub file_descriptors: Vec<protobuf::descriptor::FileDescriptorProto>,
321 }
322
323 #[doc(hidden)]
parse_and_typecheck( includes: &[&Path], input: &[&Path], ) -> io::Result<ParsedAndTypechecked>324 pub fn parse_and_typecheck(
325 includes: &[&Path],
326 input: &[&Path],
327 ) -> io::Result<ParsedAndTypechecked> {
328 let mut run = Run {
329 parsed_files: HashMap::new(),
330 includes: includes,
331 };
332
333 let mut relative_paths = Vec::new();
334
335 for input in input {
336 relative_paths.push(run.add_fs_file(&Path::new(input))?);
337 }
338
339 let file_descriptors: Vec<_> = run
340 .parsed_files
341 .into_iter()
342 .map(|(_, v)| v.descriptor)
343 .collect();
344
345 Ok(ParsedAndTypechecked {
346 relative_paths,
347 file_descriptors,
348 })
349 }
350
351 /// Like `protoc --rust_out=...` but without requiring `protoc` or `protoc-gen-rust`
352 /// commands in `$PATH`.
353 #[deprecated(since = "2.14", note = "Use Codegen instead")]
354 #[allow(deprecated)]
run(args: Args) -> io::Result<()>355 pub fn run(args: Args) -> io::Result<()> {
356 let includes: Vec<&Path> = args.includes.iter().map(|p| Path::new(p)).collect();
357 let inputs: Vec<&Path> = args.input.iter().map(|p| Path::new(p)).collect();
358 let p = parse_and_typecheck(&includes, &inputs)?;
359
360 protobuf_codegen::gen_and_write(
361 &p.file_descriptors,
362 &p.relative_paths,
363 &Path::new(&args.out_dir),
364 &args.customize,
365 )
366 }
367
368 #[cfg(test)]
369 mod test {
370 use super::*;
371
372 #[cfg(windows)]
373 #[test]
test_relative_path_to_protobuf_path_windows()374 fn test_relative_path_to_protobuf_path_windows() {
375 assert_eq!(
376 "foo/bar.proto",
377 relative_path_to_protobuf_path(&Path::new("foo\\bar.proto"))
378 );
379 }
380
381 #[test]
test_relative_path_to_protobuf_path()382 fn test_relative_path_to_protobuf_path() {
383 assert_eq!(
384 "foo/bar.proto",
385 relative_path_to_protobuf_path(&Path::new("foo/bar.proto"))
386 );
387 }
388 }
389