use crate::code_gen::CodeGenBuilder;
use proc_macro2::TokenStream;
use quote::quote;
use std::{
fs,
path::{Path, PathBuf},
};
use tonic_build::{manual, Service};
struct ServiceGenerator {
definitions: TokenStream,
}
impl ServiceGenerator {
fn generate(&mut self, service: &manual::Service, rewrite_crate: &str) {
let definition = CodeGenBuilder::new()
.emit_package(true)
.compile_well_known_types(false)
.generate_server_definition(service, rewrite_crate, "");
self.definitions.extend(definition);
}
fn finalize(&mut self, buf: &mut String) {
if !self.definitions.is_empty() {
let definitions = &self.definitions;
let server_definitions = quote::quote! {
#definitions
};
let ast: syn::File =
syn::parse2(server_definitions).expect("not a valid tokenstream");
let code = prettyplease::unparse(&ast);
buf.push_str(&code);
self.definitions = TokenStream::default();
}
}
}
#[derive(Debug, Default)]
pub struct Builder {
rewrite_crate_name: Option<String>,
out_dir: Option<PathBuf>,
emit_composite_package: bool,
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn rewrite_crate(mut self, crate_name: &str) -> Self {
self.rewrite_crate_name = Some(crate_name.to_string());
self
}
pub fn out_dir(mut self, out_dir: impl AsRef<Path>) -> Self {
self.out_dir = Some(out_dir.as_ref().to_path_buf());
self
}
pub fn emit_composite_package(mut self, emit_composite_package: bool) -> Self {
self.emit_composite_package = emit_composite_package;
self
}
pub fn compile(self, services: &[&manual::Service]) {
let out_dir = if std::env::var("DOCS_RS").is_ok() {
println!("cargo:warning=Using OUT_DIR for codegen because we're building on docs.rs");
PathBuf::from(std::env::var("OUT_DIR").unwrap())
} else if let Some(out_dir) = self.out_dir.as_ref() {
fs::create_dir_all(out_dir).unwrap_or_else(|_| {
panic!("failed to create out dir: {}", out_dir.display())
});
out_dir.clone()
} else {
PathBuf::from(std::env::var("OUT_DIR").unwrap())
};
let rewrite_crate_name = if let Some(name) = self.rewrite_crate_name.as_ref() {
name
} else {
"crate"
};
let mut generator = ServiceGenerator {
definitions: TokenStream::default(),
};
for service in services {
let mut output = String::new();
generator.generate(service, rewrite_crate_name);
generator.finalize(&mut output);
let out_file = out_dir.join(out_file(service));
fs::write(&out_file, output)
.unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
}
if self.emit_composite_package {
let out_file = out_dir.join("packages.sdk.rs");
let output = generate_composite_package(services);
let ast = syn::parse2(output).unwrap();
let code = prettyplease::unparse(&ast);
fs::write(&out_file, code)
.unwrap_or_else(|_| panic!("failed to write: {}", out_file.display()));
}
}
}
fn out_file(service: &manual::Service) -> String {
format!("{}.{}.sdk.rs", service.package(), service.name())
}
fn generate_composite_package(services: &[&manual::Service]) -> TokenStream {
let mut includes = TokenStream::new();
let mut rpc_calls = TokenStream::new();
for service in services {
let service_name = syn::Lit::Str(syn::LitStr::new(
&out_file(service),
proc_macro2::Span::call_site(),
));
includes.extend(quote! {
include!(#service_name);
});
let call = crate::server::server_fn_ident(service.name());
rpc_calls.extend(quote! { #call(), });
}
let rpcs = quote! {
vec![#rpc_calls]
};
quote! {
#includes
pub fn definitions() -> Vec<schema_builder::code_gen_types::SdkGeneratorStruct> {
#rpcs
}
}
}