synapse_codegen/
parser.rs1use crate::{MethodDef, ServiceDef};
4use anyhow::{Context, Result};
5use prost::Message;
6use prost_types::{FileDescriptorSet, MethodDescriptorProto, ServiceDescriptorProto};
7use std::{
8 path::{Path, PathBuf},
9 process::Command,
10};
11
12pub fn parse_proto_file(proto_path: &Path) -> Result<Vec<ServiceDef>> {
14 parse_proto_file_with_includes(proto_path, &[])
15}
16
17pub fn parse_proto_file_with_includes(
19 proto_path: &Path,
20 include_paths: &[PathBuf],
21) -> Result<Vec<ServiceDef>> {
22 let descriptor_set = compile_proto_to_descriptor(proto_path, include_paths)?;
24
25 let proto_filename = proto_path
27 .file_name()
28 .and_then(|s| s.to_str())
29 .unwrap_or("");
30
31 let mut services = Vec::new();
33
34 for file in &descriptor_set.file {
35 let file_name = file.name();
37 if !file_name.ends_with(proto_filename) {
38 continue;
39 }
40
41 let package = file.package().to_string();
42
43 for service in &file.service {
44 let service_def = parse_service(&package, service)?;
45 services.push(service_def);
46 }
47 }
48
49 Ok(services)
50}
51
52fn compile_proto_to_descriptor(
54 proto_path: &Path,
55 include_paths: &[PathBuf],
56) -> Result<FileDescriptorSet> {
57 let temp_dir = std::env::temp_dir();
59 let proto_hash = {
60 use std::hash::{Hash, Hasher};
61 let mut hasher = std::collections::hash_map::DefaultHasher::new();
62 proto_path.hash(&mut hasher);
63 hasher.finish()
64 };
65 let descriptor_path = temp_dir.join(format!("synapse_descriptor_{:x}.pb", proto_hash));
66
67 let protoc = protoc_bin_vendored::protoc_bin_path().context("Failed to get protoc binary")?;
69
70 let proto_dir = proto_path
72 .parent()
73 .context("Proto file has no parent directory")?;
74
75 let mut cmd = Command::new(protoc);
77 cmd.arg("--descriptor_set_out")
78 .arg(&descriptor_path)
79 .arg("--include_imports")
80 .arg(format!("--proto_path={}", proto_dir.display()));
81
82 for include_path in include_paths {
84 cmd.arg(format!("--proto_path={}", include_path.display()));
85 }
86
87 cmd.arg(proto_path);
88
89 let output = cmd.output().context("Failed to run protoc")?;
91
92 if !output.status.success() {
93 anyhow::bail!("protoc failed: {}", String::from_utf8_lossy(&output.stderr));
94 }
95
96 let descriptor_bytes =
98 std::fs::read(&descriptor_path).context("Failed to read descriptor file")?;
99
100 let descriptor_set = FileDescriptorSet::decode(&descriptor_bytes[..])
101 .context("Failed to decode descriptor set")?;
102
103 Ok(descriptor_set)
104}
105
106fn parse_service(package: &str, service: &ServiceDescriptorProto) -> Result<ServiceDef> {
108 let service_name = service.name().to_string();
109
110 let mut methods = Vec::new();
111 for method in &service.method {
112 let method_def = parse_method(method)?;
113 methods.push(method_def);
114 }
115
116 Ok(ServiceDef {
117 package: package.to_string(),
118 service_name,
119 methods,
120 })
121}
122
123fn parse_method(method: &MethodDescriptorProto) -> Result<MethodDef> {
125 Ok(MethodDef {
126 name: method.name().to_string(),
127 input_type: method.input_type().to_string(),
128 output_type: method.output_type().to_string(),
129 comment: None, })
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 #[ignore] fn test_parse_user_service() {
140 let proto_path = Path::new("../proto/proto/user_service.proto");
141 let services = parse_proto_file(proto_path).unwrap();
142
143 assert_eq!(services.len(), 1);
144 let service = &services[0];
145 assert_eq!(service.service_name, "UserService");
146 assert_eq!(service.package, "user");
147 assert_eq!(service.methods.len(), 3);
148 }
149}