use std::path::Path;
use flatbuffers_util::reflect::{GeneratorContext, MessageType};
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::quote;
pub(crate) fn compile_flatbuffers_tonic_file_only(
path: &Path,
) -> Result<(), Box<dyn std::error::Error>> {
let schema = flatbuffers_util::reflect::compile_reflection_schema(path);
let schema_ref = schema.get_ref();
let gen_ctx = flatbuffers_util::reflect::GeneratorContext::parse_from_schema(&schema_ref);
let types = gen_ctx.collect_in_out_types();
let services = gen_ctx.get_services();
println!("Processing service: {services:?}");
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let tonic_services = get_tonic_services(services);
tonic_prost_build::manual::Builder::new().compile(&tonic_services);
let content = compile_flatbuffers_tonic_file(&gen_ctx, &types);
write_tonic_flatbuffers_file(out_dir, &content, &gen_ctx.get_namespace())?;
Ok(())
}
pub(crate) fn compile_flatbuffers_tonic_file_list_only<P>(
path_list: &[P],
) -> Result<(), Box<dyn std::error::Error>>
where
P: AsRef<Path>,
{
for path in path_list {
compile_flatbuffers_tonic_file_only(path.as_ref())?;
}
Ok(())
}
fn get_tonic_services(
services: &[flatbuffers_util::reflect::Service],
) -> Vec<tonic_prost_build::manual::Service> {
let mut tonic_services = Vec::new();
for service in services {
let namespace = service.namespace.as_ref().expect("no namespace found");
let mut tonic_service = tonic_prost_build::manual::Service::builder()
.package(namespace)
.name(&service.name);
for method in &service.methods {
let method_name = get_method_name(method);
let mut tonic_method = tonic_prost_build::manual::Method::builder()
.name(&method_name)
.route_name(&method.name)
.input_type(format!("super::Owned{}", method.request_type()))
.output_type(format!("super::Owned{}", method.response_type()))
.codec_path("flatbuffers_tonic::FlatBuffersCodec");
if method.client_streaming {
tonic_method = tonic_method.client_streaming();
}
if method.server_streaming {
tonic_method = tonic_method.server_streaming();
}
tonic_service = tonic_service.method(tonic_method.build());
}
tonic_services.push(tonic_service.build());
}
tonic_services
}
fn get_method_name(method: &flatbuffers_util::reflect::Method) -> String {
let name = method.name.rsplit('.').next().unwrap().to_string();
name.to_snake_case()
}
pub fn compile_types_alias(package: &str, types: &Vec<MessageType>) -> TokenStream {
let mut content = TokenStream::new();
for t in types {
let wrapper_type = quote::format_ident!("Owned{}", t.fb_type);
let rs_type = quote::format_ident!("{}", t.fb_type);
let rs_type_mod: syn::Path = syn::parse_str(package).unwrap();
content.extend(quote! {
pub struct #wrapper_type(pub flatbuffers_tonic::OwnedFB<#rs_type_mod::#rs_type<'static>>);
impl #wrapper_type {
pub fn get_ref<'a>(&'a self) -> #rs_type_mod::#rs_type<'a> {
self.0.get_ref()
}
}
});
content.extend(quote! {
impl flatbuffers_tonic::OwnedFBCodecable for #wrapper_type {
fn new_from_bytes(bytes: bytes::Bytes) -> Result<Self, flatbuffers::InvalidFlatbuffer> {
let owned = flatbuffers_tonic::OwnedFB::<#rs_type_mod::#rs_type<'static>>::new_from_bytes(bytes)?;
Ok(Self(owned))
}
fn into_bytes(self) -> bytes::Bytes {
self.0.into_bytes()
}
}
});
}
content
}
pub fn compile_flatbuffers_tonic_file(
gen_ctx: &GeneratorContext,
types: &Vec<MessageType>,
) -> TokenStream {
let services = gen_ctx.get_services();
let package_rs = gen_ctx.get_namespace_rs();
let package = gen_ctx.get_namespace();
let mut content = TokenStream::new();
let types_content = compile_types_alias(&package_rs, types);
content.extend(quote! {
});
content.extend(quote! {
include!(concat!(env!("OUT_DIR"), "/flatbuffers/", #package, "/mod.rs"));
});
for service in services {
let namespace = service.namespace.as_ref().expect("no namespace found");
let service_name = &service.name;
content.extend(quote! {
include!(concat!(env!("OUT_DIR"), "/", #namespace, ".", #service_name, ".rs"));
});
}
content.extend(types_content);
content
}
fn write_tonic_flatbuffers_file(
out_dir: &Path,
content: &TokenStream,
package_file_suffix: &str,
) -> Result<(), Box<dyn std::error::Error>> {
use std::fs::File;
use std::io::Write;
let dest_path = out_dir.join(format!("flatbuffers_tonic.{package_file_suffix}.rs"));
let mut f = File::create(&dest_path)?;
let syntax_tree: syn::File = syn::parse2(content.clone()).unwrap_or_else(|e| {
panic!("Failed to parse generated content to syntax tree: {e} : {content}");
});
let formatted = prettyplease::unparse(&syntax_tree);
f.write_all(formatted.as_bytes())?;
Ok(())
}