use std::path::Path;
use flatbuffers_util::reflect::MessageType;
use heck::ToSnakeCase;
use proc_macro2::TokenStream;
use quote::quote;
pub(crate) fn compile_flatbuffers_tonic_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 services = flatbuffers_util::reflect::get_services_from_schema(&schema_ref);
let types = flatbuffers_util::reflect::collect_in_out_types(&services);
let out_dir = std::env::var("OUT_DIR").unwrap();
let out_dir = Path::new(&out_dir);
let package = types[0].namespace.as_ref().expect("no package found");
let tonic_services = get_tonic_services(&services);
tonic_prost_build::manual::Builder::new().compile(&tonic_services);
let content = compile_flatbuffers_tonic_file(package, &types, &services);
write_tonic_flatbuffers_file(out_dir, &content, package)?;
Ok(())
}
fn get_tonic_services(
services: &Vec<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 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")
.build();
tonic_service = tonic_service.method(tonic_method);
}
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 = quote::format_ident!("{}", package);
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_boxed(buf: Box<[u8]>) -> Result<Self, flatbuffers::InvalidFlatbuffer> {
let owned = flatbuffers_tonic::OwnedFB::<#rs_type_mod::#rs_type<'static>>::new_boxed(buf)?;
Ok(#wrapper_type(owned))
}
fn get_slice(&self) -> &[u8] {
self.0.get_slice()
}
}
});
}
content
}
pub fn compile_flatbuffers_tonic_file(
package: &str,
types: &Vec<MessageType>,
services: &Vec<flatbuffers_util::reflect::Service>,
) -> TokenStream {
let mut content = TokenStream::new();
let types_content = compile_types_alias(package, types);
content.extend(quote! {
});
content.extend(quote! {
include!(concat!(env!("OUT_DIR"), "/flatbuffers/", "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: &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}.rs"));
let mut f = File::create(&dest_path)?;
let syntax_tree: syn::File = syn::parse2(content.clone()).unwrap();
let formatted = prettyplease::unparse(&syntax_tree);
f.write_all(formatted.as_bytes())?;
Ok(())
}