Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
bmwill committed May 4, 2022
1 parent d53520f commit d78a53a
Show file tree
Hide file tree
Showing 2 changed files with 367 additions and 0 deletions.
2 changes: 2 additions & 0 deletions tonic-build/src/lib.rs
Expand Up @@ -79,6 +79,8 @@ mod prost;
#[cfg_attr(docsrs, doc(cfg(feature = "prost")))]
pub use prost::{compile_protos, configure, Builder};

pub mod rust;

/// Service code generation for client
pub mod client;
/// Service code generation for Server
Expand Down
365 changes: 365 additions & 0 deletions tonic-build/src/rust.rs
@@ -0,0 +1,365 @@
use super::{client, server, Attributes};
use proc_macro2::TokenStream;
use quote::ToTokens;
use std::{
ffi::OsString,
fs, io,
path::{Path, PathBuf},
};

#[derive(Debug, Default)]
pub struct ServiceBuilder {
/// The service name in Rust style.
pub name: Option<String>,
/// The service name as it appears in the .proto file.
pub proto_name: Option<String>,
/// The package name as it appears in the .proto file.
pub package: Option<String>,
/// The service comments.
pub comments: Vec<String>,
/// The service methods.
pub methods: Vec<Method>,
}

impl ServiceBuilder {
pub fn name<T: AsRef<str>>(mut self, name: T) -> Self {
self.name = Some(name.as_ref().to_owned());
self
}

pub fn package<T: AsRef<str>>(mut self, package: T) -> Self {
self.package = Some(package.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,
}
}
}

/// A service descriptor.
#[derive(Debug)]
pub struct Service {
/// The service name in Rust style.
pub name: String,
/// The package name as it appears in the .proto file.
pub package: String,
/// The service comments.
pub comments: Vec<String>,
/// The service methods.
pub 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
}
}

/// A service method descriptor.
#[derive(Debug)]
pub struct Method {
/// The name of the method in Rust style.
pub name: String,
/// The name of the method as should be used when constructing a route
pub proto_name: String,
/// The method comments.
pub comments: Vec<String>,
/// The input Rust type.
pub input_type: String,
/// The output Rust type.
pub output_type: String,
/// Identifies if client streams multiple client messages.
pub client_streaming: bool,
/// Identifies if server streams multiple server messages.
pub server_streaming: bool,
/// The path to the codec to use for this method
pub 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.proto_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 {
/// The name of the method in Rust style.
pub name: Option<String>,
/// The name of the method as should be used when constructing a route
pub proto_name: Option<String>,
/// The method comments.
pub comments: Vec<String>,
/// The input Rust type.
pub input_type: Option<String>,
/// The output Rust type.
pub output_type: Option<String>,
/// Identifies if client streams multiple client messages.
pub client_streaming: bool,
/// Identifies if server streams multiple server messages.
pub server_streaming: bool,
/// The path to the codec to use for this method
pub codec_path: Option<String>,
}

impl MethodBuilder {
pub fn name<T: AsRef<str>>(mut self, name: T) -> Self {
self.name = Some(name.as_ref().to_owned());
self
}

pub fn proto_name<T: AsRef<str>>(mut self, proto_name: T) -> Self {
self.proto_name = Some(proto_name.as_ref().to_owned());
self
}

pub fn input_type<T: AsRef<str>>(mut self, input_type: T) -> Self {
self.input_type = Some(input_type.as_ref().to_owned());
self
}

pub fn output_type<T: AsRef<str>>(mut self, output_type: T) -> Self {
self.output_type = Some(output_type.as_ref().to_owned());
self
}

pub fn codec_path<T: AsRef<str>>(mut self, codec_path: T) -> 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(),
proto_name: self.proto_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 = server::generate(
service,
true, // emit_package,
"", // proto_path, -- not used
false, // compile_well_known_types -- not used
&Attributes::default(),
);
self.servers.extend(server);
}

if self.builder.build_client {
let client = client::generate(
service,
true, // emit_package,
"", // proto_path, -- not used
false, // compile_well_known_types, -- not used
&Attributes::default(),
);
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();
}
}
}

struct Builder {
build_server: bool,
build_client: bool,

out_dir: Option<PathBuf>,
}

impl Builder {
/// Set the output directory to generate code to.
///
/// Defaults to the `OUT_DIR` environment variable.
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);

//TODO group services into packages and put all packages into separate files
let out_file = out_dir.join("tonic.rs");
fs::write(out_file, output).unwrap();
}
}

#[test]
fn rust_build() {
let method = Method::builder()
.name("say_hello")
.proto_name("SayHello")
.input_type("super::HelloRequest")
.output_type("super::HelloResponse")
.codec_path("crate::BincodeCodec")
.build();

let service = Service::builder()
.name("Greeter")
.package("helloworld")
.method(method)
.build();

let out_dir = PathBuf::from(std::env!("CARGO_MANIFEST_DIR")).join("src");
// .join("generated");

let mut builder = Builder {
build_server: true,
build_client: true,
out_dir: None,
}
.out_dir(out_dir);

builder.compile(&[service]);
}

0 comments on commit d78a53a

Please sign in to comment.