use quote::{format_ident, quote};
pub fn service_generator() -> Box<ServiceGenerator> {
Box::new(ServiceGenerator {})
}
struct Service {
server_name: syn::Ident,
client_name: syn::Ident,
fqn: String,
methods: Vec<Method>,
}
struct Method {
name: syn::Ident,
proto_name: String,
input_type: syn::Type,
output_type: syn::Type,
}
impl Service {
fn from_prost(s: prost_build::Service) -> Self {
let fqn = format!("{}.{}", s.package, s.proto_name);
let server_name = format_ident!("{}", &s.name);
let client_name = format_ident!("{}Client", &s.name);
let methods = s
.methods
.into_iter()
.map(|m| Method::from_prost(&s.package, &s.proto_name, m))
.collect();
Self {
server_name,
client_name,
fqn,
methods,
}
}
}
impl Method {
fn from_prost(pkg_name: &str, svc_name: &str, m: prost_build::Method) -> Self {
let as_type = |s| -> syn::Type {
let Ok(typ) = syn::parse_str::<syn::Type>(s) else {
panic!(
"twirp-build failed generated invalid Rust while processing {pkg}.{svc}/{name}). this is a bug in twirp-build, please file a GitHub issue",
pkg = pkg_name,
svc = svc_name,
name = m.proto_name,
);
};
typ
};
let input_type = as_type(&m.input_type);
let output_type = as_type(&m.output_type);
let name = format_ident!("{}", m.name);
let message = m.proto_name;
Self {
name,
proto_name: message,
input_type,
output_type,
}
}
}
pub struct ServiceGenerator;
impl prost_build::ServiceGenerator for ServiceGenerator {
fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
let service = Service::from_prost(service);
let mut trait_methods = Vec::with_capacity(service.methods.len());
let mut proxy_methods = Vec::with_capacity(service.methods.len());
for m in &service.methods {
let name = &m.name;
let input_type = &m.input_type;
let output_type = &m.output_type;
trait_methods.push(quote! {
async fn #name(&self, ctx: twirp::Context, req: #input_type) -> Result<#output_type, Self::Error>;
});
proxy_methods.push(quote! {
async fn #name(&self, ctx: twirp::Context, req: #input_type) -> Result<#output_type, Self::Error> {
T::#name(&*self, ctx, req).await
}
});
}
let server_name = &service.server_name;
let server_trait = quote! {
#[twirp::async_trait::async_trait]
pub trait #server_name {
type Error;
#(#trait_methods)*
}
#[twirp::async_trait::async_trait]
impl<T> #server_name for std::sync::Arc<T>
where
T: #server_name + Sync + Send
{
type Error = T::Error;
#(#proxy_methods)*
}
};
let mut route_calls = Vec::with_capacity(service.methods.len());
for m in &service.methods {
let name = &m.name;
let input_type = &m.input_type;
let path = format!("/{uri}", uri = m.proto_name);
route_calls.push(quote! {
.route(#path, |api: T, ctx: twirp::Context, req: #input_type| async move {
api.#name(ctx, req).await
})
});
}
let router = quote! {
pub fn router<T>(api: T) -> twirp::Router
where
T: #server_name + Clone + Send + Sync + 'static,
<T as #server_name>::Error: twirp::IntoTwirpResponse
{
twirp::details::TwirpRouterBuilder::new(api)
#(#route_calls)*
.build()
}
};
let client_name = service.client_name;
let mut client_trait_methods = Vec::with_capacity(service.methods.len());
let mut client_methods = Vec::with_capacity(service.methods.len());
for m in &service.methods {
let name = &m.name;
let input_type = &m.input_type;
let output_type = &m.output_type;
let request_path = format!("{}/{}", service.fqn, m.proto_name);
client_trait_methods.push(quote! {
async fn #name(&self, req: #input_type) -> Result<#output_type, twirp::ClientError>;
});
client_methods.push(quote! {
async fn #name(&self, req: #input_type) -> Result<#output_type, twirp::ClientError> {
self.request(#request_path, req).await
}
})
}
let client_trait = quote! {
#[twirp::async_trait::async_trait]
pub trait #client_name: Send + Sync {
#(#client_trait_methods)*
}
#[twirp::async_trait::async_trait]
impl #client_name for twirp::client::Client {
#(#client_methods)*
}
};
let service_fqn_path = format!("/{}", service.fqn);
let generated = quote! {
pub use twirp;
pub const SERVICE_FQN: &str = #service_fqn_path;
#server_trait
#router
#client_trait
};
let ast: syn::File = syn::parse2(generated)
.expect("twirp-build generated invalid Rust. this is a bug in twirp-build, please file an issue");
let code = prettyplease::unparse(&ast);
buf.push_str(&code);
}
}