1use anyhow::Result;
4use heck::ToSnakeCase;
5use proc_macro2::TokenStream;
6use quote::{format_ident, quote};
7
8pub mod build_helper;
9mod client_gen;
10pub mod parser;
11mod server_gen;
12
13pub use build_helper::{CodegenConfig, generate_synapse_code};
14pub use client_gen::generate_client_stub;
15pub use parser::{parse_proto_file, parse_proto_file_with_includes};
16pub use server_gen::generate_server_trait;
17
18#[derive(Debug, Clone)]
20pub struct ServiceDef {
21 pub package: String,
22 pub service_name: String,
23 pub methods: Vec<MethodDef>,
24}
25
26#[derive(Debug, Clone)]
28pub struct MethodDef {
29 pub name: String,
30 pub input_type: String,
31 pub output_type: String,
32 pub comment: Option<String>,
33}
34
35impl ServiceDef {
36 pub fn full_name(&self) -> String {
38 format!("{}.{}", self.package, self.service_name)
39 }
40
41 pub fn module_path(&self) -> String {
43 self.package.replace('.', "::")
44 }
45
46 pub fn interface_id_expr(&self) -> TokenStream {
48 let full_name = self.full_name();
49 quote! {
50 synapse_primitives::InterfaceId::from_name(#full_name)
51 }
52 }
53}
54
55impl MethodDef {
56 pub fn method_name_snake(&self) -> String {
58 self.name.to_snake_case()
59 }
60
61 pub fn method_id_expr(&self) -> TokenStream {
63 let name = &self.name;
64 quote! {
65 synapse_primitives::MethodId::from_name(#name)
66 }
67 }
68
69 pub fn input_type_path(&self, package: &str) -> TokenStream {
71 type_name_to_path(&self.input_type, package)
72 }
73
74 pub fn output_type_path(&self, package: &str) -> TokenStream {
76 type_name_to_path(&self.output_type, package)
77 }
78}
79
80fn type_name_to_path(type_name: &str, package: &str) -> TokenStream {
82 let type_name = type_name.strip_prefix('.').unwrap_or(type_name);
84
85 let parts: Vec<&str> = type_name.split('.').collect();
87
88 if parts.is_empty() {
89 return quote! { () };
90 }
91
92 if type_name.starts_with("synapse.") {
94 let relative_parts: Vec<_> = parts[1..].to_vec();
95 let idents: Vec<_> = relative_parts
96 .iter()
97 .map(|p| format_ident!("{}", p))
98 .collect();
99 return quote! { ::synapse_proto::#(#idents)::* };
100 }
101
102 if type_name.starts_with(package) {
104 let relative_parts: Vec<_> = parts[package.split('.').count()..].to_vec();
105 let idents: Vec<_> = relative_parts
106 .iter()
107 .map(|p| format_ident!("{}", p))
108 .collect();
109 quote! { #(#idents)::* }
110 } else {
111 let idents: Vec<_> = parts.iter().map(|p| format_ident!("{}", p)).collect();
113 quote! { #(#idents)::* }
114 }
115}
116
117pub fn generate_service_code(service: &ServiceDef) -> Result<String> {
119 let server_trait = server_gen::generate_server_trait(service)?;
120 let client_stub = client_gen::generate_client_stub(service)?;
121 let router_impl = server_gen::generate_router_impl(service)?;
122
123 let combined = quote! {
124 #server_trait
125
126 #client_stub
127
128 #router_impl
129 };
130
131 let syntax_tree = syn::parse2(combined)?;
133 let formatted = prettyplease::unparse(&syntax_tree);
134
135 Ok(formatted)
136}