Skip to main content

synapse_codegen/
lib.rs

1//! Synapse codegen - Generate server traits and client stubs from protobuf services
2
3use 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/// Service definition parsed from proto
19#[derive(Debug, Clone)]
20pub struct ServiceDef {
21    pub package: String,
22    pub service_name: String,
23    pub methods: Vec<MethodDef>,
24}
25
26/// Method definition
27#[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    /// Get the full qualified name (package.ServiceName)
37    pub fn full_name(&self) -> String {
38        format!("{}.{}", self.package, self.service_name)
39    }
40
41    /// Get the Rust module path (package parts as snake_case)
42    pub fn module_path(&self) -> String {
43        self.package.replace('.', "::")
44    }
45
46    /// Get interface ID calculation
47    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    /// Get snake_case method name for Rust
57    pub fn method_name_snake(&self) -> String {
58        self.name.to_snake_case()
59    }
60
61    /// Get method ID calculation
62    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    /// Parse input type to Rust path
70    pub fn input_type_path(&self, package: &str) -> TokenStream {
71        type_name_to_path(&self.input_type, package)
72    }
73
74    /// Parse output type to Rust path
75    pub fn output_type_path(&self, package: &str) -> TokenStream {
76        type_name_to_path(&self.output_type, package)
77    }
78}
79
80/// Convert proto type name to Rust path
81fn type_name_to_path(type_name: &str, package: &str) -> TokenStream {
82    // Remove leading dot if present
83    let type_name = type_name.strip_prefix('.').unwrap_or(type_name);
84
85    // Split into parts
86    let parts: Vec<&str> = type_name.split('.').collect();
87
88    if parts.is_empty() {
89        return quote! { () };
90    }
91
92    // Handle well-known package mappings (synapse -> synapse_proto)
93    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 it starts with our package, use relative path
103    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        // Use absolute path
112        let idents: Vec<_> = parts.iter().map(|p| format_ident!("{}", p)).collect();
113        quote! { #(#idents)::* }
114    }
115}
116
117/// Generate complete code for a service (server + client)
118pub 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    // Format the generated code
132    let syntax_tree = syn::parse2(combined)?;
133    let formatted = prettyplease::unparse(&syntax_tree);
134
135    Ok(formatted)
136}