use proc_macro2::{TokenStream, Span};
use quote::quote;
use crate::interface::parse::cooked::{InterfaceDefinition, ServiceDefinition, UpdateDefinition};
use super::{to_doc_attrs, is_unit_type, to_upper_camel_case, message_enum::generate_message_enum};
#[derive(Debug, Eq, PartialEq)]
enum UpdateKind {
RequestUpdate,
ResponseUpdate,
}
pub fn generate_services(item_tokens: &mut TokenStream, client_impl_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, interface: &InterfaceDefinition) {
for service in interface.services() {
generate_service(item_tokens, client_impl_tokens, fizyr_rpc, service, interface.visibility());
}
}
#[allow(clippy::needless_late_init)]
fn generate_service(item_tokens: &mut TokenStream, client_impl_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, service: &ServiceDefinition, visibility: &syn::Visibility) {
let service_name = service.name();
let service_doc = to_doc_attrs(service.doc());
let service_id = service.service_id();
let request_type = service.request_type();
let request_param;
let request_body;
if is_unit_type(request_type) {
request_param = None;
request_body = quote!(F::encode_body(&()))
} else {
request_param = Some(quote!(request: &#request_type));
request_body = quote!(F::encode_body(request))
}
let response_type = service.response_type();
let mut service_item_tokens = TokenStream::new();
if service.request_updates().is_empty() && service.response_updates().is_empty() {
client_impl_tokens.extend(quote! {
#service_doc
#[allow(clippy::ptr_arg)]
pub async fn #service_name(&self, #request_param) -> ::core::result::Result<#response_type, #fizyr_rpc::Error>
where
F: #fizyr_rpc::format::EncodeBody<#request_type>,
F: #fizyr_rpc::format::DecodeBody<#response_type>,
{
let request_body = #request_body.map_err(#fizyr_rpc::Error::encode_failed)?;
let mut request = self.peer.send_request(#service_id, request_body).await?;
let response = request.recv_response().await?;
if response.header.service_id == #fizyr_rpc::service_id::ERROR {
use #fizyr_rpc::Body;
let message = response.body
.into_error()
.map_err(|e| #fizyr_rpc::Error::decode_failed(::std::boxed::Box::new(e)))?;
::core::result::Result::Err(#fizyr_rpc::Error::remote_error(message))
} else {
F::decode_body(response.body).map_err(#fizyr_rpc::Error::decode_failed)
}
}
})
} else {
generate_sent_request(&mut service_item_tokens, fizyr_rpc, service);
client_impl_tokens.extend(quote! {
#service_doc
#[allow(clippy::ptr_arg)]
pub async fn #service_name(&self, #request_param) -> ::core::result::Result<#service_name::SentRequestHandle<F>, #fizyr_rpc::Error>
where
F: #fizyr_rpc::format::EncodeBody<#request_type>,
F: #fizyr_rpc::format::DecodeBody<#response_type>,
{
let request_body = #request_body.map_err(#fizyr_rpc::Error::encode_failed)?;
let mut request = self.peer.send_request(#service_id, request_body).await?;
::core::result::Result::Ok(#service_name::SentRequestHandle { request })
}
});
}
generate_received_request(&mut service_item_tokens, fizyr_rpc, service);
let mod_doc = format!("Support types for the `{}` service.", service.name());
item_tokens.extend(quote! {
#[doc = #mod_doc]
#visibility mod #service_name {
#[allow(unused_imports)]
use super::*;
#service_item_tokens
}
});
}
fn generate_sent_request(item_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, service: &ServiceDefinition) {
let service_name = service.name();
let mut read_handle_impl_tokens = TokenStream::new();
let mut write_handle_impl_tokens = TokenStream::new();
let response_type = service.response_type();
let doc_recv_update = match service.response_updates().is_empty() {
true => quote! {
},
false => quote! {
},
};
read_handle_impl_tokens.extend(quote! {
#doc_recv_update
pub async fn recv_response(&mut self) -> ::core::result::Result<#response_type, #fizyr_rpc::Error>
where
F: #fizyr_rpc::format::DecodeBody<#response_type>,
{
let response = self.request.recv_response().await?;
if response.header.service_id == #fizyr_rpc::service_id::ERROR {
use #fizyr_rpc::Body;
let message = response.body
.into_error()
.map_err(|e| #fizyr_rpc::Error::decode_failed(::std::boxed::Box::new(e)))?;
::core::result::Result::Err(#fizyr_rpc::Error::remote_error(message))
} else {
F::decode_body(response.body).map_err(#fizyr_rpc::Error::decode_failed)
}
}
});
if !service.request_updates().is_empty() {
generate_message_enum(
item_tokens,
fizyr_rpc,
service.request_updates(),
&syn::Ident::new("RequestUpdate", Span::call_site()),
&format!("A request update for the {} service", service.name()),
);
generate_send_update_functions(&mut write_handle_impl_tokens, fizyr_rpc, "e!(#service_name::RequestUpdate), service.request_updates());
}
if !service.response_updates().is_empty() {
generate_message_enum(
item_tokens,
fizyr_rpc,
service.response_updates(),
&syn::Ident::new("ResponseUpdate", Span::call_site()),
&format!("A response update for the {} service", service.name()),
);
generate_recv_update_function(&mut read_handle_impl_tokens, fizyr_rpc, service.response_updates(), UpdateKind::ResponseUpdate);
}
let handle_doc = format!("Read/write handle for a sent request for the `{}` service.", service.name());
let write_handle_doc = format!("Write handle for a sent request for the `{}` service.", service.name());
item_tokens.extend(quote! {
#[doc = #handle_doc]
pub struct SentRequestHandle<F: #fizyr_rpc::format::Format> {
pub(super) request: #fizyr_rpc::SentRequestHandle<F::Body>,
}
#[doc = #write_handle_doc]
pub struct SentRequestWriteHandle<F: #fizyr_rpc::format::Format> {
pub(super) request: #fizyr_rpc::SentRequestWriteHandle<F::Body>,
}
impl<F: #fizyr_rpc::format::Format> ::core::fmt::Debug for SentRequestHandle<F> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
f.debug_struct(::core::any::type_name::<Self>())
.field("request_id", &self.request_id())
.field("service_id", &self.service_id())
.finish_non_exhaustive()
}
}
impl<F: #fizyr_rpc::format::Format> ::core::fmt::Debug for SentRequestWriteHandle<F> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
f.debug_struct(::core::any::type_name::<Self>())
.field("request_id", &self.request_id())
.field("service_id", &self.service_id())
.finish_non_exhaustive()
}
}
impl<F: #fizyr_rpc::format::Format> ::core::clone::Clone for SentRequestWriteHandle<F> {
fn clone(&self) -> Self {
Self {
request: self.request.clone(),
}
}
}
impl<F: #fizyr_rpc::format::Format> SentRequestHandle<F> {
pub fn inner(&self) -> &#fizyr_rpc::SentRequestHandle<F::Body> {
&self.request
}
pub fn inner_mut(&self) -> &#fizyr_rpc::SentRequestHandle<F::Body> {
&self.request
}
pub fn into_inner(self) -> #fizyr_rpc::SentRequestHandle<F::Body> {
self.request
}
pub fn request_id(&self) -> u32 {
self.request.request_id()
}
pub fn service_id(&self) -> i32 {
self.request.service_id()
}
pub fn write_handle(&self) -> SentRequestWriteHandle<F> {
SentRequestWriteHandle {
request: self.request.write_handle(),
}
}
#read_handle_impl_tokens
#write_handle_impl_tokens
}
impl<F: #fizyr_rpc::format::Format> SentRequestWriteHandle<F> {
pub fn inner(&self) -> &#fizyr_rpc::SentRequestWriteHandle<F::Body> {
&self.request
}
pub fn inner_mut(&self) -> &#fizyr_rpc::SentRequestWriteHandle<F::Body> {
&self.request
}
pub fn into_inner(self) -> #fizyr_rpc::SentRequestWriteHandle<F::Body> {
self.request
}
pub fn request_id(&self) -> u32 {
self.request.request_id()
}
pub fn service_id(&self) -> i32 {
self.request.service_id()
}
#write_handle_impl_tokens
}
});
}
fn generate_received_request(item_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, service: &ServiceDefinition) {
let response_type = service.response_type();
let service_name = service.name();
let service_id = service.service_id();
let mut read_handle_impl_tokens = TokenStream::new();
let mut write_handle_impl_tokens = TokenStream::new();
if !service.response_updates().is_empty() {
generate_send_update_functions(&mut write_handle_impl_tokens, fizyr_rpc, "e!(#service_name::ResponseUpdate), service.response_updates());
}
if !service.request_updates().is_empty() {
generate_recv_update_function(&mut read_handle_impl_tokens, fizyr_rpc, service.request_updates(), UpdateKind::RequestUpdate);
}
write_handle_impl_tokens.extend(quote! {
#[allow(clippy::ptr_arg)]
pub async fn send_response(&self, response: &#response_type) -> ::core::result::Result<(), #fizyr_rpc::Error>
where
F: #fizyr_rpc::format::EncodeBody<#response_type>,
{
let encoded = F::encode_body(response).map_err(#fizyr_rpc::Error::encode_failed)?;
let _response = self.request.send_response(#service_id, encoded).await?;
::core::result::Result::Ok(())
}
pub async fn send_error_response(&self, error: &str) -> ::core::result::Result<(), #fizyr_rpc::Error> {
self.request.send_error_response(error).await
}
});
let handle_doc = format!("Handle for a received `{}` request.", service.name());
let write_handle_doc = format!("Write-only handle for a received `{}` request.", service.name());
item_tokens.extend(quote! {
#[doc = #handle_doc]
pub struct ReceivedRequestHandle<F: #fizyr_rpc::format::Format> {
pub(super) request: #fizyr_rpc::ReceivedRequestHandle<F::Body>,
}
#[doc = #write_handle_doc]
pub struct ReceivedRequestWriteHandle<F: #fizyr_rpc::format::Format> {
pub(super) request: #fizyr_rpc::ReceivedRequestWriteHandle<F::Body>,
}
impl<F: #fizyr_rpc::format::Format> ::core::fmt::Debug for ReceivedRequestHandle<F> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
f.debug_struct(::core::any::type_name::<Self>())
.field("request_id", &self.request_id())
.field("service_id", &self.service_id())
.finish_non_exhaustive()
}
}
impl<F: #fizyr_rpc::format::Format> ::core::fmt::Debug for ReceivedRequestWriteHandle<F> {
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
f.debug_struct(::core::any::type_name::<Self>())
.field("request_id", &self.request_id())
.field("service_id", &self.service_id())
.finish_non_exhaustive()
}
}
impl<F: #fizyr_rpc::format::Format> ::core::clone::Clone for ReceivedRequestWriteHandle<F> {
fn clone(&self) -> Self {
Self {
request: self.request.clone(),
}
}
}
impl<F: #fizyr_rpc::format::Format> ReceivedRequestHandle<F> {
pub fn inner(&self) -> &#fizyr_rpc::ReceivedRequestHandle<F::Body> {
&self.request
}
pub fn inner_mut(&self) -> &#fizyr_rpc::ReceivedRequestHandle<F::Body> {
&self.request
}
pub fn into_inner(self) -> #fizyr_rpc::ReceivedRequestHandle<F::Body> {
self.request
}
pub fn request_id(&self) -> u32 {
self.request.request_id()
}
pub fn service_id(&self) -> i32 {
self.request.service_id()
}
pub fn write_handle(&self) -> ReceivedRequestWriteHandle<F> {
ReceivedRequestWriteHandle {
request: self.request.write_handle(),
}
}
#read_handle_impl_tokens
#write_handle_impl_tokens
}
impl<F: #fizyr_rpc::format::Format> ReceivedRequestWriteHandle<F> {
pub fn inner(&self) -> &#fizyr_rpc::ReceivedRequestWriteHandle<F::Body> {
&self.request
}
pub fn inner_mut(&self) -> &#fizyr_rpc::ReceivedRequestWriteHandle<F::Body> {
&self.request
}
pub fn into_inner(self) -> #fizyr_rpc::ReceivedRequestWriteHandle<F::Body> {
self.request
}
pub fn request_id(&self) -> u32 {
self.request.request_id()
}
pub fn service_id(&self) -> i32 {
self.request.service_id()
}
#write_handle_impl_tokens
}
})
}
fn generate_send_update_functions(impl_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, enum_type: &TokenStream, updates: &[UpdateDefinition]) {
quote! {
#[allow(clippy::ptr_arg)]
pub async fn send_update(&self, update: &#enum_type) -> ::core::result::Result<(), #fizyr_rpc::Error>
where
#enum_type: #fizyr_rpc::format::ToMessage<F>,
{
let (service_id, body) = F::encode_message(update).map_err(#fizyr_rpc::Error::encode_failed)?;
self.request.send_update(service_id, body).await?;
::core::result::Result::Ok(())
}
};
for update in updates {
let function_name = syn::Ident::new(&format!("send_{}_update", update.name()), Span::call_site());
let body_type = update.body_type();
let service_id = update.service_id();
let doc = format!("Send a `{}` update to the remote peer.", update.name());
let body_arg;
let body_val;
if is_unit_type(body_type) {
body_arg = None;
body_val = quote!(&());
} else {
body_arg = Some(quote!(update: &#body_type));
body_val = quote!(update);
}
impl_tokens.extend(quote! {
#[doc = #doc]
#[allow(clippy::ptr_arg)]
pub async fn #function_name(&self, #body_arg) -> ::core::result::Result<(), #fizyr_rpc::Error>
where
F: #fizyr_rpc::format::EncodeBody<#body_type>,
{
let body = F::encode_body(#body_val).map_err(#fizyr_rpc::Error::encode_failed)?;
self.request.send_update(#service_id, body).await?;
::core::result::Result::Ok(())
}
})
}
}
fn generate_recv_update_function(impl_tokens: &mut TokenStream, fizyr_rpc: &syn::Ident, updates: &[UpdateDefinition], kind: UpdateKind) {
let mut doc = quote! {
};
if kind == UpdateKind::ResponseUpdate {
doc.extend(quote! {
})
}
let update_kind = match kind {
UpdateKind::RequestUpdate => quote!(RequestUpdate),
UpdateKind::ResponseUpdate => quote!(ResponseUpdate),
};
let mut decode_arms = TokenStream::new();
let mut where_clause = TokenStream::new();
for update in updates {
let service_id = update.service_id();
let body_type = update.body_type();
let variant_name = syn::Ident::new(&to_upper_camel_case(&update.name().to_string()), Span::call_site());
where_clause.extend(quote! {
F: #fizyr_rpc::format::DecodeBody<#body_type>,
});
decode_arms.extend(quote! {
#service_id => {
match F::decode_body(update.body) {
::core::result::Result::Ok(body) => {
::core::result::Result::Ok(#update_kind::#variant_name(body))
},
::core::result::Result::Err(e) => {
::core::result::Result::Err(#fizyr_rpc::ParseUpdateError::InvalidUpdate(update.header, e))
},
}
},
});
}
impl_tokens.extend(quote! {
#doc
pub async fn recv_update(&mut self) -> ::core::option::Option<::core::result::Result<#update_kind, #fizyr_rpc::ParseUpdateError<F::Body>>>
where
#where_clause
{
let update = self.request.recv_update().await?;
::core::option::Option::Some(match update.header.service_id {
#decode_arms
_ => ::core::result::Result::Err(#fizyr_rpc::ParseUpdateError::UnknownUpdate(update))
})
}
});
}