use anyhow::Result;
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub mod build_helper;
mod client_gen;
pub mod parser;
mod server_gen;
pub use build_helper::{CodegenConfig, generate_synapse_code};
pub use client_gen::generate_client_stub;
pub use parser::{parse_proto_file, parse_proto_file_with_includes};
pub use server_gen::generate_server_trait;
#[derive(Debug, Clone)]
pub struct ServiceDef {
pub package: String,
pub service_name: String,
pub methods: Vec<MethodDef>,
}
#[derive(Debug, Clone)]
pub struct MethodDef {
pub name: String,
pub input_type: String,
pub output_type: String,
pub comment: Option<String>,
}
impl ServiceDef {
pub fn full_name(&self) -> String {
format!("{}.{}", self.package, self.service_name)
}
pub fn module_path(&self) -> String {
self.package.replace('.', "::")
}
pub fn interface_id_expr(&self) -> TokenStream {
let full_name = self.full_name();
quote! {
synapse_primitives::InterfaceId::from_name(#full_name)
}
}
}
impl MethodDef {
pub fn method_name_snake(&self) -> String {
self.name.to_snake_case()
}
pub fn method_id_expr(&self) -> TokenStream {
let name = &self.name;
quote! {
synapse_primitives::MethodId::from_name(#name)
}
}
pub fn input_type_path(&self, package: &str) -> TokenStream {
type_name_to_path(&self.input_type, package)
}
pub fn output_type_path(&self, package: &str) -> TokenStream {
type_name_to_path(&self.output_type, package)
}
}
fn type_name_to_path(type_name: &str, package: &str) -> TokenStream {
let type_name = type_name.strip_prefix('.').unwrap_or(type_name);
let parts: Vec<&str> = type_name.split('.').collect();
if parts.is_empty() {
return quote! { () };
}
if type_name.starts_with("synapse.") {
let relative_parts: Vec<_> = parts[1..].to_vec();
let idents: Vec<_> = relative_parts
.iter()
.map(|p| format_ident!("{}", p))
.collect();
return quote! { ::synapse_proto::#(#idents)::* };
}
if type_name.starts_with(package) {
let relative_parts: Vec<_> = parts[package.split('.').count()..].to_vec();
let idents: Vec<_> = relative_parts
.iter()
.map(|p| format_ident!("{}", p))
.collect();
quote! { #(#idents)::* }
} else {
let idents: Vec<_> = parts.iter().map(|p| format_ident!("{}", p)).collect();
quote! { #(#idents)::* }
}
}
pub fn generate_service_code(service: &ServiceDef) -> Result<String> {
let server_trait = server_gen::generate_server_trait(service)?;
let client_stub = client_gen::generate_client_stub(service)?;
let router_impl = server_gen::generate_router_impl(service)?;
let combined = quote! {
#server_trait
#client_stub
#router_impl
};
let syntax_tree = syn::parse2(combined)?;
let formatted = prettyplease::unparse(&syntax_tree);
Ok(formatted)
}