Move codegen to build and remove macros

This commit is contained in:
Lucio Franco
2019-09-06 15:19:18 -04:00
parent 295e7321b9
commit d0cfafafc6
20 changed files with 916 additions and 615 deletions
+137
View File
@@ -0,0 +1,137 @@
use proc_macro2::TokenStream;
use prost_build::{Method, Service};
use quote::{format_ident, quote};
use syn::Path;
pub(crate) fn generate(service: &Service, proto: &str) -> TokenStream {
let service_ident = quote::format_ident!("{}Client", service.name);
let methods = generate_methods(service, proto);
quote! {
use tonic::_codegen::*;
pub struct #service_ident <T> {
inner: tonic::client::Grpc<T>,
}
impl<T> #service_ident <T>
where T: tonic::client::GrpcService<tonic::BoxBody>,
T::ResponseBody: tonic::body::Body + tonic::_codegen::HttpBody + Send + 'static,
T::Error: Into<tonic::error::Error>,
<T::ResponseBody as tonic::_codegen::HttpBody>::Error: Into<tonic::error::Error> + Send,
<T::ResponseBody as tonic::_codegen::HttpBody>::Data: Into<bytes::Bytes> + Send, {
pub fn new(inner: T) -> Self {
let inner = tonic::client::Grpc::new(inner);
Self { inner }
}
pub async fn ready(&mut self) -> Result<(), tonic::Status> {
self.inner.ready().await.map_err(|e| {
tonic::Status::new(tonic::Code::Unknown, format!("Service was not ready: {}", e.into()))
})
}
#methods
}
impl<T: Clone> Clone for #service_ident <T> {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
}
}
}
}
}
fn generate_methods(service: &Service, proto: &str) -> TokenStream {
let mut stream = TokenStream::new();
for method in &service.methods {
let path = format!(
"/{}.{}/{}",
service.package, service.proto_name, method.proto_name
);
let method = match (method.client_streaming, method.server_streaming) {
(false, false) => generate_unary(method, &proto, path),
(false, true) => generate_server_streaming(method, &proto, path),
(true, false) => generate_client_streaming(method, &proto, path),
(true, true) => generate_streaming(method, &proto, path),
};
stream.extend(method);
}
stream
}
fn generate_unary(method: &Method, proto: &str, path: String) -> TokenStream {
let ident = format_ident!("{}", method.name);
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
quote! {
pub async fn #ident (&mut self, request: tonic::Request<#request>)
-> Result<tonic::Response<#response>, tonic::Status> {
self.ready().await?;
let codec = tonic::codec::ProstCodec::new();
let path = http::uri::PathAndQuery::from_static(#path);
self.inner.unary(request, path, codec).await
}
}
}
fn generate_server_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
let ident = format_ident!("{}", method.name);
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
quote! {
pub async fn #ident (&mut self, request: tonic::Request<#request>)
-> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status> {
self.ready().await?;
let codec = tonic::codec::ProstCodec::new();
let path = http::uri::PathAndQuery::from_static(#path);
self.inner.server_streaming(request, path, codec).await
}
}
}
fn generate_client_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
let ident = format_ident!("{}", method.name);
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
quote! {
pub async fn #ident <S>(&mut self, request: tonic::Request<S>)
-> Result<tonic::Response<#response>, tonic::Status>
where S: tonic::_codegen::Stream<Item = Result<#request, tonic::Status>> + Send + 'static,
{
self.ready().await?;
let codec = tonic::codec::ProstCodec::new();
let path = http::uri::PathAndQuery::from_static(#path);
let request = request.map(|s| Box::pin(s));
self.inner.client_streaming(request, path, codec).await
}
}
}
fn generate_streaming(method: &Method, proto: &str, path: String) -> TokenStream {
let ident = format_ident!("{}", method.name);
let request: Path = syn::parse_str(&format!("{}::{}", proto, method.input_type)).unwrap();
let response: Path = syn::parse_str(&format!("{}::{}", proto, method.output_type)).unwrap();
quote! {
pub async fn #ident <S>(&mut self, request: tonic::Request<S>)
-> Result<tonic::Response<tonic::codec::Streaming<#response>>, tonic::Status>
where S: tonic::_codegen::Stream<Item = Result<#request, tonic::Status>> + Send + 'static,
{
self.ready().await?;
let codec = tonic::codec::ProstCodec::new();
let path = http::uri::PathAndQuery::from_static(#path);
let request = request.map(|s| Box::pin(s));
self.inner.streaming(request, path, codec).await
}
}
}
+33 -78
View File
@@ -1,95 +1,50 @@
use prost_build::Config;
use serde::Serialize;
use std::{io, path};
use std::{io, path, process::Command};
pub fn compile_protos<P>(protos: &[P], includes: &[P]) -> io::Result<()>
mod client;
mod service;
pub fn compile_protos<P>(protos: &[P], includes: &[P], package: &str) -> io::Result<()>
where
P: AsRef<path::Path>,
{
let out_dir = std::env::var("OUT_DIR").unwrap();
let mut config = Config::new();
config.service_generator(Box::new(ServiceGenerator {}));
config.out_dir(&out_dir);
config.compile_protos(protos, includes)?;
config.compile_protos(protos, includes)
fmt(&out_dir, &format!("{}.rs", package));
Ok(())
}
fn fmt(out_dir: &str, file: &str) {
let out = Command::new("rustfmt")
.arg("--emit")
.arg("files")
.arg("--edition")
.arg("2018")
.arg(format!("{}/{}", out_dir, file))
.output()
.unwrap();
println!("out: {:?}", out);
assert!(out.status.success());
}
pub struct ServiceGenerator {}
impl prost_build::ServiceGenerator for ServiceGenerator {
fn generate(&mut self, service: prost_build::Service, _buf: &mut String) {
let file = format!(
"{}/{}.{}.json",
std::env::var("OUT_DIR").unwrap(),
service.package,
service.name
);
fn generate(&mut self, service: prost_build::Service, buf: &mut String) {
let path = "self";
let server = service::generate(&service, path);
let code = format!("{}", server);
buf.push_str(&code);
let svc = Service {
name: service.name,
proto_name: service.proto_name,
package: service.package,
methods: service
.methods
.into_iter()
.map(|m| Method {
name: m.name,
proto_name: m.proto_name,
input_type: m.input_type,
output_type: m.output_type,
input_proto_type: m.input_proto_type,
output_proto_type: m.output_proto_type,
client_streaming: m.client_streaming,
server_streaming: m.server_streaming,
})
.collect(),
};
let json = serde_json::to_string(&svc).unwrap();
std::fs::write(file, json).unwrap();
let client = client::generate(&service, path);
let code = format!("{}", client);
buf.push_str(&code);
}
// fn finalize(&mut self, buf: &mut String) {
// let mut fmt = codegen::Formatter::new(buf);
// self.scope
// .fmt(&mut fmt)
// .expect("formatting root scope failed!");
// self.scope = codegen::Scope::new();
// }
}
/// A service descriptor.
#[derive(Debug, Serialize)]
pub struct Service {
/// The service name in Rust style.
pub name: String,
/// The service name as it appears in the .proto file.
pub proto_name: String,
/// The package name as it appears in the .proto file.
pub package: String,
/// The service methods.
pub methods: Vec<Method>,
}
/// A service method descriptor.
#[derive(Debug, Serialize)]
pub struct Method {
/// The name of the method in Rust style.
pub name: String,
/// The name of the method as it appears in the .proto file.
pub proto_name: String,
/// The input Rust type.
pub input_type: String,
/// The output Rust type.
pub output_type: String,
/// The input Protobuf type.
pub input_proto_type: String,
/// The output Protobuf type.
pub output_proto_type: String,
// /// The method options.
// pub options: prost_types::MethodOptions,
/// Identifies if client streams multiple client messages.
pub client_streaming: bool,
/// Identifies if server streams multiple server messages.
pub server_streaming: bool,
}
+355
View File
@@ -0,0 +1,355 @@
use proc_macro2::{Span, TokenStream};
use prost_build::{Method, Service};
use quote::quote;
use syn::{Ident, Lit, LitStr, Path};
pub(crate) fn generate(service: &Service, proto_path: &str) -> TokenStream {
let methods = generate_methods(&service, proto_path);
let server_make_service = quote::format_ident!("{}Server", service.name);
let server_service = quote::format_ident!("{}ServerSvc", service.name);
let server_trait = quote::format_ident!("{}", service.name);
let generated_trait = generate_trait(service, proto_path, server_trait.clone());
quote! {
use tonic::_codegen::*;
#generated_trait
// TODO: impl debug
#[derive(Clone)]
pub struct #server_make_service <T: #server_trait > {
inner: std::sync::Arc<T>,
}
// TODO: impl debug
pub struct #server_service <T: #server_trait > {
inner: std::sync::Arc<T>,
}
impl<T: #server_trait > #server_make_service <T> {
pub fn new(inner: T) -> Self {
let inner = std::sync::Arc::new(inner);
Self { inner }
}
}
impl<T: #server_trait > #server_service <T> {
pub fn new(inner: std::sync::Arc<T>) -> Self {
Self { inner }
}
}
impl<T: #server_trait , R> Service<R> for #server_make_service <T> {
type Response = #server_service <T>;
type Error = tonic::error::Never;
type Future = Ready<Result<Self::Response, Self::Error>>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, _: R) -> Self::Future {
ok(#server_service ::new(self.inner.clone()))
}
}
impl<T: #server_trait > Service<http::Request<tonic::_codegen::HyperBody>> for #server_service <T> {
type Response = http::Response<tonic::BoxBody>;
type Error = tonic::error::Never;
type Future = BoxFuture<Self::Response, Self::Error>;
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
Poll::Ready(Ok(()))
}
fn call(&mut self, req: http::Request<tonic::_codegen::HyperBody>) -> Self::Future {
let inner = self.inner.clone();
match req.uri().path() {
#methods
_ => unimplemented!("use grpc unimplemented"),
}
}
}
}
}
fn generate_trait(service: &Service, proto_path: &str, server_trait: Ident) -> TokenStream {
let methods = generate_trait_methods(service, proto_path);
quote! {
#[async_trait]
pub trait #server_trait : Send + Sync + 'static {
#methods
}
}
}
fn generate_trait_methods(service: &Service, proto_path: &str) -> TokenStream {
let mut stream = TokenStream::new();
for method in &service.methods {
let name = quote::format_ident!("{}", method.name);
let req_message: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.input_type)).unwrap();
let res_message: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.output_type)).unwrap();
let method = match (method.client_streaming, method.server_streaming) {
(false, false) => {
quote! {
async fn #name (&self, request: tonic::Request<#req_message>)
-> Result<tonic::Response<#res_message>, tonic::Status>;
}
}
(true, false) => {
quote! {
async fn #name (&self, request: tonic::Request<tonic::Streaming<#req_message>>)
-> Result<tonic::Response<#res_message>, tonic::Status>;
}
}
(false, true) => {
let stream = quote::format_ident!("{}Stream", method.proto_name);
quote! {
type #stream: Stream<Item = Result<#res_message, tonic::Status>> + Unpin + Send + 'static;
async fn #name (&self, request: tonic::Request<#req_message>)
-> Result<tonic::Response<Self::#stream>, tonic::Status>;
}
}
(true, true) => {
let stream = quote::format_ident!("{}Stream", method.proto_name);
quote! {
type #stream: Stream<Item = Result<#res_message, tonic::Status>> + Unpin + Send + 'static;
async fn #name (&self, request: tonic::Request<tonic::Streaming<#req_message>>)
-> Result<tonic::Response<Self::#stream>, tonic::Status>;
}
}
};
stream.extend(method);
}
stream
}
fn generate_methods(service: &Service, proto_path: &str) -> TokenStream {
let mut stream = TokenStream::new();
for method in &service.methods {
let path = format!(
"/{}.{}/{}",
service.package, service.proto_name, method.proto_name
);
let method_path = Lit::Str(LitStr::new(&path, Span::call_site()));
let ident = quote::format_ident!("{}", method.name);
let server_trait = quote::format_ident!("{}", service.name);
let method_stream = match (method.client_streaming, method.server_streaming) {
(false, false) => generate_unary(method, ident, proto_path, server_trait),
(false, true) => {
generate_server_streaming(method, ident.clone(), proto_path, server_trait)
}
(true, false) => {
generate_client_streaming(method, ident.clone(), proto_path, server_trait)
}
(true, true) => generate_streaming(method, ident.clone(), proto_path, server_trait),
};
let method = quote! {
#method_path => {
#method_stream
}
};
stream.extend(method);
}
stream
}
fn generate_unary(
method: &Method,
method_ident: Ident,
proto_path: &str,
server_trait: Ident,
) -> TokenStream {
let service_ident = Ident::new(&method.proto_name, Span::call_site());
let request: Path = syn::parse_str(&format!("{}::{}", proto_path, method.input_type)).unwrap();
let response: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.output_type)).unwrap();
quote! {
struct #service_ident <T: #server_trait >(pub std::sync::Arc<T>);
impl<T: #server_trait > tonic::server::UnaryService<#request> for #service_ident <T> {
type Response = #response;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(&mut self, request: tonic::Request<#request>) -> Self::Future {
let inner = self.0.clone();
let fut = async move {
inner.#method_ident(request).await
};
Box::pin(fut)
}
}
let inner = self.inner.clone();
let fut = async move {
let method = #service_ident(inner);
let codec = tonic::codec::ProstCodec::new();
let mut grpc = tonic::server::Grpc::new(codec);
let res = grpc.unary(method, req).await;
Ok(res)
};
// TODO: implement this future manually
Box::pin(fut)
}
}
fn generate_server_streaming(
method: &Method,
method_ident: Ident,
proto_path: &str,
server_trait: Ident,
) -> TokenStream {
let service_ident = Ident::new(&method.proto_name, Span::call_site());
let request: Path = syn::parse_str(&format!("{}::{}", proto_path, method.input_type)).unwrap();
let response: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.output_type)).unwrap();
let response_stream = quote::format_ident!("{}Stream", method.proto_name);
// TODO: parse response stream type, if it is a concrete type then use that
// as the ResponseStream type, if it is a impl Trait then we need to box.
quote! {
struct #service_ident <T: #server_trait >(pub std::sync::Arc<T>);
impl<T: #server_trait > tonic::server::ServerStreamingService<#request> for #service_ident <T> {
type Response = #response;
type ResponseStream = T::#response_stream;
type Future = BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
fn call(&mut self, request: tonic::Request<#request>) -> Self::Future {
let inner = self.0.clone();
let fut = async move {
inner.#method_ident(request).await
};
Box::pin(fut)
}
}
let inner = self.inner.clone();
let fut = async move {
let method = #service_ident(inner);
let codec = tonic::codec::ProstCodec::new();
let mut grpc = tonic::server::Grpc::new(codec);
let res = grpc.server_streaming(method, req).await;
Ok(res)
};
Box::pin(fut)
}
}
fn generate_client_streaming(
method: &Method,
method_ident: Ident,
proto_path: &str,
server_trait: Ident,
) -> TokenStream {
let service_ident = Ident::new(&method.proto_name, Span::call_site());
let request: Path = syn::parse_str(&format!("{}::{}", proto_path, method.input_type)).unwrap();
let response: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.output_type)).unwrap();
quote! {
struct #service_ident<T: #server_trait >(pub std::sync::Arc<T>);
impl<T: #server_trait> tonic::server::ClientStreamingService<#request> for #service_ident <T>
{
type Response = #response;
type Future = BoxFuture<tonic::Response<Self::Response>, tonic::Status>;
fn call(&mut self, request: tonic::Request<tonic::Streaming<#request>>) -> Self::Future {
let inner = self.0.clone();
let fut = async move {
inner.#method_ident(request).await
};
Box::pin(fut)
}
}
let inner = self.inner.clone();
let fut = async move {
let method = #service_ident(inner);
let codec = tonic::codec::ProstCodec::new();
let mut grpc = tonic::server::Grpc::new(codec);
let res = grpc.client_streaming(method, req).await;
Ok(res)
};
Box::pin(fut)
}
}
fn generate_streaming(
method: &Method,
method_ident: Ident,
proto_path: &str,
server_trait: Ident,
) -> TokenStream {
let service_ident = Ident::new(&method.proto_name, Span::call_site());
let request: Path = syn::parse_str(&format!("{}::{}", proto_path, method.input_type)).unwrap();
let response: Path =
syn::parse_str(&format!("{}::{}", proto_path, method.output_type)).unwrap();
let response_stream = quote::format_ident!("{}Stream", method.proto_name);
// TODO: parse response stream type, if it is a concrete type then use that
// as the ResponseStream type, if it is a impl Trait then we need to box.
quote! {
struct #service_ident<T: #server_trait >(pub std::sync::Arc<T>);
impl<T: #server_trait > tonic::server::StreamingService<#request> for #service_ident <T>
{
type Response = #response;
type ResponseStream = T::#response_stream;
type Future = BoxFuture<tonic::Response<Self::ResponseStream>, tonic::Status>;
fn call(&mut self, request: tonic::Request<tonic::Streaming<#request>>) -> Self::Future {
let inner = self.0.clone();
let fut = async move {
inner.#method_ident(request).await
};
Box::pin(fut)
}
}
let inner = self.inner.clone();
let fut = async move {
let method = #service_ident(inner);
let codec = tonic::codec::ProstCodec::new();
let mut grpc = tonic::server::Grpc::new(codec);
let res = grpc.streaming(method, req).await;
Ok(res)
};
Box::pin(fut)
}
}