use crate::code_gen::CodeGenBuilder;
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::{
fs,
path::{Path, PathBuf},
};
#[derive(Debug, Default)]
pub struct ServiceBuilder {
name: Option<String>,
package: Option<String>,
comments: Vec<String>,
methods: Vec<Method>,
}
impl ServiceBuilder {
pub fn name(mut self, name: impl AsRef<str>) -> Self {
self.name = Some(name.as_ref().to_owned());
self
}
pub fn package(mut self, package: impl AsRef<str>) -> Self {
self.package = Some(package.as_ref().to_owned());
self
}
pub fn comment(mut self, comment: impl AsRef<str>) -> Self {
self.comments.push(comment.as_ref().to_owned());
self
}
pub fn method(mut self, method: Method) -> Self {
self.methods.push(method);
self
}
pub fn build(self) -> Service {
Service {
name: self.name.unwrap(),
comments: self.comments,
package: self.package.unwrap(),
methods: self.methods,
}
}
}
#[derive(Debug)]
pub struct Service {
name: String,
package: String,
comments: Vec<String>,
methods: Vec<Method>,
}
impl Service {
pub fn builder() -> ServiceBuilder {
ServiceBuilder::default()
}
}
impl crate::Service for Service {
type Comment = String;
type Method = Method;
fn name(&self) -> &str {
&self.name
}
fn package(&self) -> &str {
&self.package
}
fn identifier(&self) -> &str {
&self.name
}
fn methods(&self) -> &[Self::Method] {
&self.methods
}
fn comment(&self) -> &[Self::Comment] {
&self.comments
}
}
#[derive(Debug)]
pub struct Method {
name: String,
route_name: String,
comments: Vec<String>,
input_type: String,
output_type: String,
client_streaming: bool,
server_streaming: bool,
codec_path: String,
}
impl Method {
pub fn builder() -> MethodBuilder {
MethodBuilder::default()
}
}
impl crate::Method for Method {
type Comment = String;
fn name(&self) -> &str {
&self.name
}
fn identifier(&self) -> &str {
&self.route_name
}
fn codec_path(&self) -> &str {
&self.codec_path
}
fn client_streaming(&self) -> bool {
self.client_streaming
}
fn server_streaming(&self) -> bool {
self.server_streaming
}
fn comment(&self) -> &[Self::Comment] {
&self.comments
}
fn request_response_name(
&self,
_proto_path: &str,
_compile_well_known_types: bool,
) -> (TokenStream, TokenStream) {
let request = syn::parse_str::<syn::Path>(&self.input_type)
.unwrap()
.to_token_stream();
let response = syn::parse_str::<syn::Path>(&self.output_type)
.unwrap()
.to_token_stream();
(request, response)
}
}
#[derive(Debug, Default)]
pub struct MethodBuilder {
name: Option<String>,
route_name: Option<String>,
comments: Vec<String>,
input_type: Option<String>,
output_type: Option<String>,
client_streaming: bool,
server_streaming: bool,
codec_path: Option<String>,
}
impl MethodBuilder {
pub fn name(mut self, name: impl AsRef<str>) -> Self {
self.name = Some(name.as_ref().to_owned());
self
}
pub fn route_name(mut self, route_name: impl AsRef<str>) -> Self {
self.route_name = Some(route_name.as_ref().to_owned());
self
}
pub fn comment(mut self, comment: impl AsRef<str>) -> Self {
self.comments.push(comment.as_ref().to_owned());
self
}
pub fn input_type(mut self, input_type: impl AsRef<str>) -> Self {
self.input_type = Some(input_type.as_ref().to_owned());
self
}
pub fn output_type(mut self, output_type: impl AsRef<str>) -> Self {
self.output_type = Some(output_type.as_ref().to_owned());
self
}
pub fn codec_path(mut self, codec_path: impl AsRef<str>) -> Self {
self.codec_path = Some(codec_path.as_ref().to_owned());
self
}
pub fn client_streaming(mut self) -> Self {
self.client_streaming = true;
self
}
pub fn server_streaming(mut self) -> Self {
self.server_streaming = true;
self
}
pub fn build(self) -> Method {
Method {
name: self.name.unwrap(),
route_name: self.route_name.unwrap(),
comments: self.comments,
input_type: self.input_type.unwrap(),
output_type: self.output_type.unwrap(),
client_streaming: self.client_streaming,
server_streaming: self.server_streaming,
codec_path: self.codec_path.unwrap(),
}
}
}
struct ServiceGenerator {
builder: Builder,
clients: TokenStream,
servers: TokenStream,
}
impl ServiceGenerator {
fn generate(&mut self, service: &Service) {
if self.builder.build_server {
let server = CodeGenBuilder::new()
.emit_package(true)
.compile_well_known_types(false)
.generate_server(service, "");
self.servers.extend(server);
}
if self.builder.build_client {
let client = CodeGenBuilder::new()
.emit_package(true)
.compile_well_known_types(false)
.build_transport(self.builder.build_transport)
.generate_client(service, "");
self.clients.extend(client);
}
}
fn finalize(&mut self, buf: &mut String) {
if self.builder.build_client && !self.clients.is_empty() {
let clients = &self.clients;
let client_service = quote::quote! {
#clients
};
let ast: syn::File = syn::parse2(client_service).expect("not a valid tokenstream");
let code = prettyplease::unparse(&ast);
buf.push_str(&code);
self.clients = TokenStream::default();
}
if self.builder.build_server && !self.servers.is_empty() {
let servers = &self.servers;
let server_service = quote::quote! {
#servers
};
let ast: syn::File = syn::parse2(server_service).expect("not a valid tokenstream");
let code = prettyplease::unparse(&ast);
buf.push_str(&code);
self.servers = TokenStream::default();
}
}
}
#[derive(Debug)]
pub struct Builder {
build_server: bool,
build_client: bool,
build_transport: bool,
out_dir: Option<PathBuf>,
}
impl Default for Builder {
fn default() -> Self {
Self {
build_server: true,
build_client: true,
build_transport: true,
out_dir: None,
}
}
}
impl Builder {
pub fn new() -> Self {
Self::default()
}
pub fn build_client(mut self, enable: bool) -> Self {
self.build_client = enable;
self
}
pub fn build_server(mut self, enable: bool) -> Self {
self.build_server = enable;
self
}
pub fn build_transport(mut self, enable: bool) -> Self {
self.build_transport = enable;
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 compile(self, services: &[Service]) {
let out_dir = if let Some(out_dir) = self.out_dir.as_ref() {
out_dir.clone()
} else {
PathBuf::from(std::env::var("OUT_DIR").unwrap())
};
let mut generator = ServiceGenerator {
builder: self,
clients: TokenStream::default(),
servers: TokenStream::default(),
};
for service in services {
generator.generate(service);
let mut output = String::new();
generator.finalize(&mut output);
let out_file = out_dir.join(format!("{}.{}.rs", service.package, service.name));
fs::write(out_file, output).unwrap();
}
}
}