#![deny(missing_docs)]
#![deny(intra_doc_link_resolution_failure)]
extern crate protobuf;
extern crate protobuf_codegen;
mod convert;
use std::collections::HashMap;
use std::error::Error;
use std::fmt;
use std::fs;
use std::io;
use std::io::Read;
use std::path::Path;
mod model;
mod parser;
mod str_lit;
pub use protobuf_codegen::Customize;
#[cfg(test)]
mod test_against_protobuf_protos;
#[derive(Debug, Default)]
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: HashMap<String, FileDescriptorPair>,
includes: &'a [&'a str],
}
impl<'a> Run<'a> {
fn get_file_and_all_deps_already_parsed(
&self,
protobuf_path: &str,
result: &mut HashMap<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 HashMap<String, FileDescriptorPair>,
) {
for import in &parsed.import_paths {
self.get_file_and_all_deps_already_parsed(import, 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)?;
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.import_paths {
self.add_imported_file(import_path)?;
}
let mut this_file_deps = HashMap::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);
}
}
Err(io::Error::new(
io::ErrorKind::Other,
format!(
"protobuf path {:?} is not found in import path {:?}",
protobuf_path, self.includes
),
))
}
fn add_fs_file(&mut self, fs_path: &Path) -> io::Result<String> {
let relative_path = self
.includes
.iter()
.filter_map(|include_dir| fs_path.strip_prefix(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: &[&str], input: &[&str]) -> io::Result<ParsedAndTypechecked> {
let mut run = Run {
parsed_files: HashMap::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,
})
}
pub fn run(args: Args) -> io::Result<()> {
let p = parse_and_typecheck(args.includes, args.input)?;
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"))
);
}
}