use crate::{MethodDef, ServiceDef};
use anyhow::{Context, Result};
use prost::Message;
use prost_types::{FileDescriptorSet, MethodDescriptorProto, ServiceDescriptorProto};
use std::{
path::{Path, PathBuf},
process::Command,
};
pub fn parse_proto_file(proto_path: &Path) -> Result<Vec<ServiceDef>> {
parse_proto_file_with_includes(proto_path, &[])
}
pub fn parse_proto_file_with_includes(
proto_path: &Path,
include_paths: &[PathBuf],
) -> Result<Vec<ServiceDef>> {
let descriptor_set = compile_proto_to_descriptor(proto_path, include_paths)?;
let proto_filename = proto_path
.file_name()
.and_then(|s| s.to_str())
.unwrap_or("");
let mut services = Vec::new();
for file in &descriptor_set.file {
let file_name = file.name();
if !file_name.ends_with(proto_filename) {
continue;
}
let package = file.package().to_string();
for service in &file.service {
let service_def = parse_service(&package, service)?;
services.push(service_def);
}
}
Ok(services)
}
fn compile_proto_to_descriptor(
proto_path: &Path,
include_paths: &[PathBuf],
) -> Result<FileDescriptorSet> {
let temp_dir = std::env::temp_dir();
let proto_hash = {
use std::hash::{Hash, Hasher};
let mut hasher = std::collections::hash_map::DefaultHasher::new();
proto_path.hash(&mut hasher);
hasher.finish()
};
let descriptor_path = temp_dir.join(format!("synapse_descriptor_{:x}.pb", proto_hash));
let protoc = protoc_bin_vendored::protoc_bin_path().context("Failed to get protoc binary")?;
let proto_dir = proto_path
.parent()
.context("Proto file has no parent directory")?;
let mut cmd = Command::new(protoc);
cmd.arg("--descriptor_set_out")
.arg(&descriptor_path)
.arg("--include_imports")
.arg(format!("--proto_path={}", proto_dir.display()));
for include_path in include_paths {
cmd.arg(format!("--proto_path={}", include_path.display()));
}
cmd.arg(proto_path);
let output = cmd.output().context("Failed to run protoc")?;
if !output.status.success() {
anyhow::bail!("protoc failed: {}", String::from_utf8_lossy(&output.stderr));
}
let descriptor_bytes =
std::fs::read(&descriptor_path).context("Failed to read descriptor file")?;
let descriptor_set = FileDescriptorSet::decode(&descriptor_bytes[..])
.context("Failed to decode descriptor set")?;
Ok(descriptor_set)
}
fn parse_service(package: &str, service: &ServiceDescriptorProto) -> Result<ServiceDef> {
let service_name = service.name().to_string();
let mut methods = Vec::new();
for method in &service.method {
let method_def = parse_method(method)?;
methods.push(method_def);
}
Ok(ServiceDef {
package: package.to_string(),
service_name,
methods,
})
}
fn parse_method(method: &MethodDescriptorProto) -> Result<MethodDef> {
Ok(MethodDef {
name: method.name().to_string(),
input_type: method.input_type().to_string(),
output_type: method.output_type().to_string(),
comment: None, })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[ignore] fn test_parse_user_service() {
let proto_path = Path::new("../proto/proto/user_service.proto");
let services = parse_proto_file(proto_path).unwrap();
assert_eq!(services.len(), 1);
let service = &services[0];
assert_eq!(service.service_name, "UserService");
assert_eq!(service.package, "user");
assert_eq!(service.methods.len(), 3);
}
}