#![deny(missing_docs)]
#![deny(broken_intra_doc_links)]
extern crate protobuf;
extern crate protobuf_codegen;
mod convert;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;
use std::path::PathBuf;
use std::path::StripPrefixError;
mod linked_hash_map;
mod model;
mod parser;
use linked_hash_map::LinkedHashMap;
pub use protobuf_codegen::Customize;
#[cfg(test)]
mod test_against_protobuf_protos;
#[derive(Debug, Default)]
pub struct Codegen {
out_dir: PathBuf,
includes: Vec<PathBuf>,
inputs: Vec<PathBuf>,
customize: Customize,
}
impl Codegen {
pub fn new() -> Self {
Self::default()
}
pub fn out_dir(&mut self, out_dir: impl AsRef<Path>) -> &mut Self {
self.out_dir = out_dir.as_ref().to_owned();
self
}
pub fn include(&mut self, include: impl AsRef<Path>) -> &mut Self {
self.includes.push(include.as_ref().to_owned());
self
}
pub fn includes(&mut self, includes: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
for include in includes {
self.include(include);
}
self
}
pub fn input(&mut self, input: impl AsRef<Path>) -> &mut Self {
self.inputs.push(input.as_ref().to_owned());
self
}
pub fn inputs(&mut self, inputs: impl IntoIterator<Item = impl AsRef<Path>>) -> &mut Self {
for input in inputs {
self.input(input);
}
self
}
pub fn customize(&mut self, customize: Customize) -> &mut Self {
self.customize = customize;
self
}
pub fn run(&self) -> io::Result<()> {
let includes: Vec<&Path> = self.includes.iter().map(|p| p.as_path()).collect();
let inputs: Vec<&Path> = self.inputs.iter().map(|p| p.as_path()).collect();
let p = parse_and_typecheck(&includes, &inputs)?;
protobuf_codegen::gen_and_write(
&p.file_descriptors,
&p.relative_paths,
&self.out_dir,
&self.customize,
)
}
}
#[derive(Debug, Default)]
#[deprecated(since = "2.14", note = "Use Codegen object instead")]
pub struct Args<'a> {
pub out_dir: &'a str,
pub includes: &'a [&'a str],
pub input: &'a [&'a str],
pub customize: Customize,
}
pub(crate) fn relative_path_to_protobuf_path(path: &Path) -> String {
assert!(path.is_relative());
let path = path.to_str().expect("not a valid UTF-8 name");
if cfg!(windows) {
path.replace('\\', "/")
} else {
path.to_owned()
}
}
#[derive(Clone)]
struct FileDescriptorPair {
parsed: model::FileDescriptor,
descriptor: protobuf::descriptor::FileDescriptorProto,
}
#[derive(Debug)]
enum CodegenError {
ParserErrorWithLocation(parser::ParserErrorWithLocation),
ConvertError(convert::ConvertError),
}
impl From<parser::ParserErrorWithLocation> for CodegenError {
fn from(e: parser::ParserErrorWithLocation) -> Self {
CodegenError::ParserErrorWithLocation(e)
}
}
impl From<convert::ConvertError> for CodegenError {
fn from(e: convert::ConvertError) -> Self {
CodegenError::ConvertError(e)
}
}
#[derive(Debug)]
struct WithFileError {
file: String,
error: CodegenError,
}
impl fmt::Display for WithFileError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "WithFileError")
}
}
impl Error for WithFileError {
fn description(&self) -> &str {
"WithFileError"
}
}
struct Run<'a> {
parsed_files: LinkedHashMap<String, FileDescriptorPair>,
includes: &'a [&'a Path],
}
impl<'a> Run<'a> {
fn get_file_and_all_deps_already_parsed(
&self,
protobuf_path: &str,
result: &mut LinkedHashMap<String, FileDescriptorPair>,
) {
if let Some(_) = result.get(protobuf_path) {
return;
}
let pair = self
.parsed_files
.get(protobuf_path)
.expect("must be already parsed");
result.insert(protobuf_path.to_owned(), pair.clone());
self.get_all_deps_already_parsed(&pair.parsed, result);
}
fn get_all_deps_already_parsed(
&self,
parsed: &model::FileDescriptor,
result: &mut LinkedHashMap<String, FileDescriptorPair>,
) {
for import in &parsed.imports {
self.get_file_and_all_deps_already_parsed(&import.path, result);
}
}
fn add_file(&mut self, protobuf_path: &str, fs_path: &Path) -> io::Result<()> {
if let Some(_) = self.parsed_files.get(protobuf_path) {
return Ok(());
}
let mut content = String::new();
fs::File::open(fs_path)?.read_to_string(&mut content)?;
self.add_file_content(protobuf_path, fs_path, &content)
}
fn add_file_content(
&mut self,
protobuf_path: &str,
fs_path: &Path,
content: &str,
) -> io::Result<()> {
let parsed = model::FileDescriptor::parse(content).map_err(|e| {
io::Error::new(
io::ErrorKind::Other,
WithFileError {
file: format!("{}", fs_path.display()),
error: e.into(),
},
)
})?;
for import_path in &parsed.imports {
self.add_imported_file(&import_path.path)?;
}
let mut this_file_deps = LinkedHashMap::new();
self.get_all_deps_already_parsed(&parsed, &mut this_file_deps);
let this_file_deps: Vec<_> = this_file_deps.into_iter().map(|(_, v)| v.parsed).collect();
let descriptor =
convert::file_descriptor(protobuf_path.to_owned(), &parsed, &this_file_deps).map_err(
|e| {
io::Error::new(
io::ErrorKind::Other,
WithFileError {
file: format!("{}", fs_path.display()),
error: e.into(),
},
)
},
)?;
self.parsed_files.insert(
protobuf_path.to_owned(),
FileDescriptorPair { parsed, descriptor },
);
Ok(())
}
fn add_imported_file(&mut self, protobuf_path: &str) -> io::Result<()> {
for include_dir in self.includes {
let fs_path = Path::new(include_dir).join(protobuf_path);
if fs_path.exists() {
return self.add_file(protobuf_path, &fs_path);
}
}
let embedded = match protobuf_path {
"rustproto.proto" => Some(RUSTPROTO_PROTO),
"google/protobuf/any.proto" => Some(ANY_PROTO),
"google/protobuf/api.proto" => Some(API_PROTO),
"google/protobuf/descriptor.proto" => Some(DESCRIPTOR_PROTO),
"google/protobuf/duration.proto" => Some(DURATION_PROTO),
"google/protobuf/empty.proto" => Some(EMPTY_PROTO),
"google/protobuf/field_mask.proto" => Some(FIELD_MASK_PROTO),
"google/protobuf/source_context.proto" => Some(SOURCE_CONTEXT_PROTO),
"google/protobuf/struct.proto" => Some(STRUCT_PROTO),
"google/protobuf/timestamp.proto" => Some(TIMESTAMP_PROTO),
"google/protobuf/type.proto" => Some(TYPE_PROTO),
"google/protobuf/wrappers.proto" => Some(WRAPPERS_PROTO),
_ => None,
};
match embedded {
Some(content) => {
self.add_file_content(protobuf_path, Path::new(protobuf_path), content)
}
None => Err(io::Error::new(
io::ErrorKind::Other,
format!(
"protobuf path {:?} is not found in import path {:?}",
protobuf_path, self.includes
),
)),
}
}
fn strip_prefix<'b>(path: &'b Path, prefix: &Path) -> Result<&'b Path, StripPrefixError> {
if prefix == Path::new(".") {
Ok(path)
} else {
path.strip_prefix(prefix)
}
}
fn add_fs_file(&mut self, fs_path: &Path) -> io::Result<String> {
let relative_path = self
.includes
.iter()
.filter_map(|include_dir| Self::strip_prefix(fs_path, include_dir).ok())
.next();
match relative_path {
Some(relative_path) => {
let protobuf_path = relative_path_to_protobuf_path(relative_path);
self.add_file(&protobuf_path, fs_path)?;
Ok(protobuf_path)
}
None => Err(io::Error::new(
io::ErrorKind::Other,
format!(
"file {:?} must reside in include path {:?}",
fs_path, self.includes
),
)),
}
}
}
#[doc(hidden)]
pub struct ParsedAndTypechecked {
pub relative_paths: Vec<String>,
pub file_descriptors: Vec<protobuf::descriptor::FileDescriptorProto>,
}
#[doc(hidden)]
pub fn parse_and_typecheck(
includes: &[&Path],
input: &[&Path],
) -> io::Result<ParsedAndTypechecked> {
let mut run = Run {
parsed_files: LinkedHashMap::new(),
includes: includes,
};
let mut relative_paths = Vec::new();
for input in input {
relative_paths.push(run.add_fs_file(&Path::new(input))?);
}
let file_descriptors: Vec<_> = run
.parsed_files
.into_iter()
.map(|(_, v)| v.descriptor)
.collect();
Ok(ParsedAndTypechecked {
relative_paths,
file_descriptors,
})
}
const RUSTPROTO_PROTO: &str = include_str!("proto/rustproto.proto");
const ANY_PROTO: &str = include_str!("proto/google/protobuf/any.proto");
const API_PROTO: &str = include_str!("proto/google/protobuf/api.proto");
const DESCRIPTOR_PROTO: &str = include_str!("proto/google/protobuf/descriptor.proto");
const DURATION_PROTO: &str = include_str!("proto/google/protobuf/duration.proto");
const EMPTY_PROTO: &str = include_str!("proto/google/protobuf/empty.proto");
const FIELD_MASK_PROTO: &str = include_str!("proto/google/protobuf/field_mask.proto");
const SOURCE_CONTEXT_PROTO: &str = include_str!("proto/google/protobuf/source_context.proto");
const STRUCT_PROTO: &str = include_str!("proto/google/protobuf/struct.proto");
const TIMESTAMP_PROTO: &str = include_str!("proto/google/protobuf/timestamp.proto");
const TYPE_PROTO: &str = include_str!("proto/google/protobuf/type.proto");
const WRAPPERS_PROTO: &str = include_str!("proto/google/protobuf/wrappers.proto");
#[deprecated(since = "2.14", note = "Use Codegen instead")]
#[allow(deprecated)]
pub fn run(args: Args) -> io::Result<()> {
let includes: Vec<&Path> = args.includes.iter().map(|p| Path::new(p)).collect();
let inputs: Vec<&Path> = args.input.iter().map(|p| Path::new(p)).collect();
let p = parse_and_typecheck(&includes, &inputs)?;
protobuf_codegen::gen_and_write(
&p.file_descriptors,
&p.relative_paths,
&Path::new(&args.out_dir),
&args.customize,
)
}
#[cfg(test)]
mod test {
use super::*;
#[cfg(windows)]
#[test]
fn test_relative_path_to_protobuf_path_windows() {
assert_eq!(
"foo/bar.proto",
relative_path_to_protobuf_path(&Path::new("foo\\bar.proto"))
);
}
#[test]
fn test_relative_path_to_protobuf_path() {
assert_eq!(
"foo/bar.proto",
relative_path_to_protobuf_path(&Path::new("foo/bar.proto"))
);
}
}