//! This crate implement protobuf codegen. //! //! This crate: //! * provides `protoc-gen-rust` plugin for `protoc` command //! * implement protobuf codegen //! //! This crate is not meant to be used directly, in fact, it does not provide any public API //! (except for `protoc-gen-rust` binary). //! //! Code can be generated with either: //! * `protoc-gen-rust` binary or //! * `protoc-rust` crate (codegen which depends on `protoc` binary for parsing) //! * `protobuf-codegen-pure` crate #![deny(broken_intra_doc_links)] #![deny(missing_docs)] extern crate protobuf; use std::collections::hash_map::HashMap; use std::fmt::Write as FmtWrite; use std::fs::File; use std::io; use std::io::Write; use std::path::Path; use protobuf::compiler_plugin; use protobuf::descriptor::*; use protobuf::Message; mod customize; mod enums; mod extensions; mod field; mod file; mod file_and_mod; mod file_descriptor; #[doc(hidden)] pub mod float; mod inside; mod message; mod oneof; mod protobuf_name; mod rust_name; mod rust_types_values; mod serde; mod well_known_types; pub(crate) mod rust; pub(crate) mod scope; pub(crate) mod strx; pub(crate) mod syntax; use customize::customize_from_rustproto_for_file; #[doc(hidden)] pub use customize::Customize; pub mod code_writer; use self::code_writer::CodeWriter; use self::enums::*; use self::extensions::*; use self::message::*; use inside::protobuf_crate_path; use scope::FileScope; use scope::RootScope; use crate::file::proto_path_to_rust_mod; #[doc(hidden)] pub use protobuf_name::ProtobufAbsolutePath; #[doc(hidden)] pub use protobuf_name::ProtobufIdent; #[doc(hidden)] pub use protobuf_name::ProtobufRelativePath; fn escape_byte(s: &mut String, b: u8) { if b == b'\n' { write!(s, "\\n").unwrap(); } else if b == b'\r' { write!(s, "\\r").unwrap(); } else if b == b'\t' { write!(s, "\\t").unwrap(); } else if b == b'\\' || b == b'"' { write!(s, "\\{}", b as char).unwrap(); } else if b == b'\0' { write!(s, "\\0").unwrap(); // ASCII printable except space } else if b > 0x20 && b < 0x7f { write!(s, "{}", b as char).unwrap(); } else { write!(s, "\\x{:02x}", b).unwrap(); } } fn write_file_descriptor_data( file: &FileDescriptorProto, customize: &Customize, w: &mut CodeWriter, ) { let fdp_bytes = file.write_to_bytes().unwrap(); w.write_line("static file_descriptor_proto_data: &'static [u8] = b\"\\"); w.indented(|w| { const MAX_LINE_LEN: usize = 72; let mut s = String::new(); for &b in &fdp_bytes { let prev_len = s.len(); escape_byte(&mut s, b); let truncate = s.len() > MAX_LINE_LEN; if truncate { s.truncate(prev_len); } if truncate || s.len() == MAX_LINE_LEN { write!(s, "\\").unwrap(); w.write_line(&s); s.clear(); } if truncate { escape_byte(&mut s, b); } } if !s.is_empty() { write!(s, "\\").unwrap(); w.write_line(&s); s.clear(); } }); w.write_line("\";"); w.write_line(""); w.lazy_static( "file_descriptor_proto_lazy", &format!( "{}::descriptor::FileDescriptorProto", protobuf_crate_path(customize) ), customize, ); w.write_line(""); w.def_fn( &format!( "parse_descriptor_proto() -> {}::descriptor::FileDescriptorProto", protobuf_crate_path(customize) ), |w| { w.write_line(&format!( "{}::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()", protobuf_crate_path(customize) )); }, ); w.write_line(""); w.pub_fn( &format!( "file_descriptor_proto() -> &'static {}::descriptor::FileDescriptorProto", protobuf_crate_path(customize) ), |w| { w.block("file_descriptor_proto_lazy.get(|| {", "})", |w| { w.write_line("parse_descriptor_proto()"); }); }, ); } struct GenFileResult { compiler_plugin_result: compiler_plugin::GenResult, mod_name: String, } fn gen_file( file: &FileDescriptorProto, _files_map: &HashMap<&str, &FileDescriptorProto>, root_scope: &RootScope, customize: &Customize, ) -> GenFileResult { // TODO: use it let mut customize = customize.clone(); // options specified in invocation have precedence over options specified in file customize.update_with(&customize_from_rustproto_for_file(file.get_options())); let scope = FileScope { file_descriptor: file, } .to_scope(); let lite_runtime = customize.lite_runtime.unwrap_or_else(|| { file.get_options().get_optimize_for() == FileOptions_OptimizeMode::LITE_RUNTIME }); let mut v = Vec::new(); { let mut w = CodeWriter::new(&mut v); w.write_generated_by("rust-protobuf", "2.22.1"); w.write_line(&format!("//! Generated file from `{}`", file.get_name())); if customize.inside_protobuf != Some(true) { w.write_line(""); w.write_line("/// Generated files are compatible only with the same version"); w.write_line("/// of protobuf runtime."); w.commented(|w| { w.write_line(&format!( "const _PROTOBUF_VERSION_CHECK: () = {}::{};", protobuf_crate_path(&customize), protobuf::VERSION_IDENT )); }) } for message in &scope.get_messages() { // ignore map entries, because they are not used in map fields if message.map_entry().is_none() { w.write_line(""); MessageGen::new(message, &root_scope, &customize).write(&mut w); } } for enum_type in &scope.get_enums() { w.write_line(""); EnumGen::new(enum_type, file, &customize, root_scope).write(&mut w); } write_extensions(file, &root_scope, &mut w, &customize); if !lite_runtime { w.write_line(""); write_file_descriptor_data(file, &customize, &mut w); } } GenFileResult { compiler_plugin_result: compiler_plugin::GenResult { name: format!("{}.rs", proto_path_to_rust_mod(file.get_name())), content: v, }, mod_name: proto_path_to_rust_mod(file.get_name()).into_string(), } } fn gen_mod_rs(mods: &[String]) -> compiler_plugin::GenResult { let mut v = Vec::new(); let mut w = CodeWriter::new(&mut v); w.comment("@generated"); w.write_line(""); for m in mods { w.write_line(&format!("pub mod {};", m)); } drop(w); compiler_plugin::GenResult { name: "mod.rs".to_owned(), content: v, } } // This function is also used externally by cargo plugin // https://github.com/plietar/rust-protobuf-build // So be careful changing its signature. #[doc(hidden)] pub fn gen( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], customize: &Customize, ) -> Vec { let root_scope = RootScope { file_descriptors: file_descriptors, }; let mut results: Vec = Vec::new(); let files_map: HashMap<&str, &FileDescriptorProto> = file_descriptors.iter().map(|f| (f.get_name(), f)).collect(); let all_file_names: Vec<&str> = file_descriptors.iter().map(|f| f.get_name()).collect(); let mut mods = Vec::new(); for file_name in files_to_generate { let file = files_map.get(&file_name[..]).expect(&format!( "file not found in file descriptors: {:?}, files: {:?}", file_name, all_file_names )); let gen_file_result = gen_file(file, &files_map, &root_scope, customize); results.push(gen_file_result.compiler_plugin_result); mods.push(gen_file_result.mod_name); } if customize.gen_mod_rs.unwrap_or(false) { results.push(gen_mod_rs(&mods)); } results } #[doc(hidden)] pub fn gen_and_write( file_descriptors: &[FileDescriptorProto], files_to_generate: &[String], out_dir: &Path, customize: &Customize, ) -> io::Result<()> { let results = gen(file_descriptors, files_to_generate, customize); for r in &results { let mut file_path = out_dir.to_owned(); file_path.push(&r.name); let mut file_writer = File::create(&file_path)?; file_writer.write_all(&r.content)?; file_writer.flush()?; } Ok(()) } #[doc(hidden)] pub fn protoc_gen_rust_main() { compiler_plugin::plugin_main_2(|r| { let customize = Customize::parse_from_parameter(r.parameter).expect("parse options"); gen(r.file_descriptors, r.files_to_generate, &customize) }); } /// Used in protobuf-codegen-identical-test #[doc(hidden)] pub fn proto_name_to_rs(name: &str) -> String { format!("{}.rs", proto_path_to_rust_mod(name)) }