• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! This crate implement protobuf codegen.
2 //!
3 //! This crate:
4 //! * provides `protoc-gen-rust` plugin for `protoc` command
5 //! * implement protobuf codegen
6 //!
7 //! This crate is not meant to be used directly, in fact, it does not provide any public API
8 //! (except for `protoc-gen-rust` binary).
9 //!
10 //! Code can be generated with either:
11 //! * `protoc-gen-rust` binary or
12 //! * `protoc-rust` crate (codegen which depends on `protoc` binary for parsing)
13 //! * `protobuf-codegen-pure` crate
14 
15 #![deny(broken_intra_doc_links)]
16 #![deny(missing_docs)]
17 
18 extern crate protobuf;
19 
20 use std::collections::hash_map::HashMap;
21 use std::fmt::Write as FmtWrite;
22 use std::fs::File;
23 use std::io;
24 use std::io::Write;
25 use std::path::Path;
26 
27 use protobuf::compiler_plugin;
28 use protobuf::descriptor::*;
29 use protobuf::Message;
30 
31 mod customize;
32 mod enums;
33 mod extensions;
34 mod field;
35 mod file;
36 mod file_and_mod;
37 mod file_descriptor;
38 #[doc(hidden)]
39 pub mod float;
40 mod inside;
41 mod message;
42 mod oneof;
43 mod protobuf_name;
44 mod rust_name;
45 mod rust_types_values;
46 mod serde;
47 mod well_known_types;
48 
49 pub(crate) mod rust;
50 pub(crate) mod scope;
51 pub(crate) mod strx;
52 pub(crate) mod syntax;
53 
54 use customize::customize_from_rustproto_for_file;
55 #[doc(hidden)]
56 pub use customize::Customize;
57 
58 pub mod code_writer;
59 
60 use self::code_writer::CodeWriter;
61 use self::enums::*;
62 use self::extensions::*;
63 use self::message::*;
64 use inside::protobuf_crate_path;
65 use scope::FileScope;
66 use scope::RootScope;
67 
68 use crate::file::proto_path_to_rust_mod;
69 
70 #[doc(hidden)]
71 pub use protobuf_name::ProtobufAbsolutePath;
72 #[doc(hidden)]
73 pub use protobuf_name::ProtobufIdent;
74 #[doc(hidden)]
75 pub use protobuf_name::ProtobufRelativePath;
76 
escape_byte(s: &mut String, b: u8)77 fn escape_byte(s: &mut String, b: u8) {
78     if b == b'\n' {
79         write!(s, "\\n").unwrap();
80     } else if b == b'\r' {
81         write!(s, "\\r").unwrap();
82     } else if b == b'\t' {
83         write!(s, "\\t").unwrap();
84     } else if b == b'\\' || b == b'"' {
85         write!(s, "\\{}", b as char).unwrap();
86     } else if b == b'\0' {
87         write!(s, "\\0").unwrap();
88     // ASCII printable except space
89     } else if b > 0x20 && b < 0x7f {
90         write!(s, "{}", b as char).unwrap();
91     } else {
92         write!(s, "\\x{:02x}", b).unwrap();
93     }
94 }
95 
write_file_descriptor_data( file: &FileDescriptorProto, customize: &Customize, w: &mut CodeWriter, )96 fn write_file_descriptor_data(
97     file: &FileDescriptorProto,
98     customize: &Customize,
99     w: &mut CodeWriter,
100 ) {
101     let fdp_bytes = file.write_to_bytes().unwrap();
102     w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\");
103     w.indented(|w| {
104         const MAX_LINE_LEN: usize = 72;
105 
106         let mut s = String::new();
107         for &b in &fdp_bytes {
108             let prev_len = s.len();
109             escape_byte(&mut s, b);
110             let truncate = s.len() > MAX_LINE_LEN;
111             if truncate {
112                 s.truncate(prev_len);
113             }
114             if truncate || s.len() == MAX_LINE_LEN {
115                 write!(s, "\\").unwrap();
116                 w.write_line(&s);
117                 s.clear();
118             }
119             if truncate {
120                 escape_byte(&mut s, b);
121             }
122         }
123         if !s.is_empty() {
124             write!(s, "\\").unwrap();
125             w.write_line(&s);
126             s.clear();
127         }
128     });
129     w.write_line("\";");
130     w.write_line("");
131     w.lazy_static(
132         "file_descriptor_proto_lazy",
133         &format!(
134             "{}::descriptor::FileDescriptorProto",
135             protobuf_crate_path(customize)
136         ),
137         customize,
138     );
139     w.write_line("");
140     w.def_fn(
141         &format!(
142             "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto",
143             protobuf_crate_path(customize)
144         ),
145         |w| {
146             w.write_line(&format!(
147                 "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()",
148                 protobuf_crate_path(customize)
149             ));
150         },
151     );
152     w.write_line("");
153     w.pub_fn(
154         &format!(
155             "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto",
156             protobuf_crate_path(customize)
157         ),
158         |w| {
159             w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| {
160                 w.write_line("parse_descriptor_proto()");
161             });
162         },
163     );
164 }
165 
166 struct GenFileResult {
167     compiler_plugin_result: compiler_plugin::GenResult,
168     mod_name: String,
169 }
170 
gen_file( file: &FileDescriptorProto, _files_map: &HashMap<&str, &FileDescriptorProto>, root_scope: &RootScope, customize: &Customize, ) -> GenFileResult171 fn gen_file(
172     file: &FileDescriptorProto,
173     _files_map: &HashMap<&str, &FileDescriptorProto>,
174     root_scope: &RootScope,
175     customize: &Customize,
176 ) -> GenFileResult {
177     // TODO: use it
178     let mut customize = customize.clone();
179     // options specified in invocation have precedence over options specified in file
180     customize.update_with(&customize_from_rustproto_for_file(file.get_options()));
181 
182     let scope = FileScope {
183         file_descriptor: file,
184     }
185     .to_scope();
186     let lite_runtime = customize.lite_runtime.unwrap_or_else(|| {
187         file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME
188     });
189 
190     let mut v = Vec::new();
191 
192     {
193         let mut w = CodeWriter::new(&mut v);
194 
195         w.write_generated_by("rust-protobuf", "2.22.1");
196         w.write_line(&format!("//! Generated file from `{}`", file.get_name()));
197         if customize.inside_protobuf != Some(true) {
198             w.write_line("");
199             w.write_line("/// Generated files are compatible only with the same version");
200             w.write_line("/// of protobuf runtime.");
201             w.commented(|w| {
202                 w.write_line(&format!(
203                     "const _PROTOBUF_VERSION_CHECK: () = {}::{};",
204                     protobuf_crate_path(&customize),
205                     protobuf::VERSION_IDENT
206                 ));
207             })
208         }
209 
210         for message in &scope.get_messages() {
211             // ignore map entries, because they are not used in map fields
212             if message.map_entry().is_none() {
213                 w.write_line("");
214                 MessageGen::new(message, &root_scope, &customize).write(&mut w);
215             }
216         }
217         for enum_type in &scope.get_enums() {
218             w.write_line("");
219             EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w);
220         }
221 
222         write_extensions(file, &root_scope, &mut w, &customize);
223 
224         if !lite_runtime {
225             w.write_line("");
226             write_file_descriptor_data(file, &customize, &mut w);
227         }
228     }
229 
230     GenFileResult {
231         compiler_plugin_result: compiler_plugin::GenResult {
232             name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())),
233             content: v,
234         },
235         mod_name: proto_path_to_rust_mod(file.get_name()).into_string(),
236     }
237 }
238 
gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult239 fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult {
240     let mut v = Vec::new();
241     let mut w = CodeWriter::new(&mut v);
242     w.comment("@generated");
243     w.write_line("");
244     for m in mods {
245         w.write_line(&format!("pub mod {};", m));
246     }
247     drop(w);
248     compiler_plugin::GenResult {
249         name: "mod.rs".to_owned(),
250         content: v,
251     }
252 }
253 
254 // This function is also used externally by cargo plugin
255 // https://github.com/plietar/rust-protobuf-build
256 // So be careful changing its signature.
257 #[doc(hidden)]
gen( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], customize: &Customize, ) -> Vec<compiler_plugin::GenResult>258 pub fn gen(
259     file_descriptors: &[FileDescriptorProto],
260     files_to_generate: &[String],
261     customize: &Customize,
262 ) -> Vec<compiler_plugin::GenResult> {
263     let root_scope = RootScope {
264         file_descriptors: file_descriptors,
265     };
266 
267     let mut results: Vec<compiler_plugin::GenResult> = Vec::new();
268     let files_map: HashMap<&str, &FileDescriptorProto> =
269         file_descriptors.iter().map(|f| (f.get_name(), f)).collect();
270 
271     let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect();
272 
273     let mut mods = Vec::new();
274 
275     for file_name in files_to_generate {
276         let file = files_map.get(&file_name[..]).expect(&format!(
277             "file not found in file descriptors: {:?}, files: {:?}",
278             file_name, all_file_names
279         ));
280 
281         let gen_file_result = gen_file(file, &files_map, &root_scope, customize);
282         results.push(gen_file_result.compiler_plugin_result);
283         mods.push(gen_file_result.mod_name);
284     }
285 
286     if customize.gen_mod_rs.unwrap_or(false) {
287         results.push(gen_mod_rs(&mods));
288     }
289 
290     results
291 }
292 
293 #[doc(hidden)]
gen_and_write( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], out_dir: &Path, customize: &Customize, ) -> io::Result<()>294 pub fn gen_and_write(
295     file_descriptors: &[FileDescriptorProto],
296     files_to_generate: &[String],
297     out_dir: &Path,
298     customize: &Customize,
299 ) -> io::Result<()> {
300     let results = gen(file_descriptors, files_to_generate, customize);
301 
302     for r in &results {
303         let mut file_path = out_dir.to_owned();
304         file_path.push(&r.name);
305         let mut file_writer = File::create(&file_path)?;
306         file_writer.write_all(&r.content)?;
307         file_writer.flush()?;
308     }
309 
310     Ok(())
311 }
312 
313 #[doc(hidden)]
protoc_gen_rust_main()314 pub fn protoc_gen_rust_main() {
315     compiler_plugin::plugin_main_2(|r| {
316         let customize = Customize::parse_from_parameter(r.parameter).expect("parse options");
317         gen(r.file_descriptors, r.files_to_generate, &customize)
318     });
319 }
320 
321 /// Used in protobuf-codegen-identical-test
322 #[doc(hidden)]
proto_name_to_rs(name: &str) -> String323 pub fn proto_name_to_rs(name: &str) -> String {
324     format!("{}.rs", proto_path_to_rust_mod(name))
325 }
326