use std::collections::{HashMap, HashSet};
use proc_macro2::{Ident, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::{Error, GenericParam, ItemImpl, Type};
use super::{attrs::ServiceAttrs, method::MethodInfo};
use crate::utils::convert_type_lifetimes;
struct HandleBodyContext<'a> {
crate_path: &'a TokenStream,
method_call_name: &'a Ident,
user_methods_name: &'a Ident,
reply_params_name: &'a Ident,
reply_error_name: &'a Ident,
reply_stream_params_name: &'a Ident,
reply_stream_name: &'a Ident,
error_type_map: &'a HashMap<String, usize>,
stream_item_type_map: &'a HashMap<String, Ident>,
interfaces: &'a [String],
type_name: &'a str,
needs_stream_boxing: bool,
}
fn extract_type_name(ty: &Type) -> Option<String> {
match ty {
Type::Path(type_path) => type_path
.path
.segments
.last()
.map(|seg| seg.ident.to_string()),
_ => None,
}
}
fn collect_interfaces(methods_info: &[MethodInfo]) -> Vec<String> {
let mut seen = HashSet::new();
let mut interfaces = Vec::new();
for method in methods_info {
if let Some(ref iface) = method.interface {
if seen.insert(iface.clone()) {
interfaces.push(iface.clone());
}
}
}
interfaces
}
pub(super) fn generate_service_impl(
item_impl: &ItemImpl,
methods_info: &[MethodInfo],
service_attrs: &ServiceAttrs,
) -> Result<TokenStream, Error> {
let crate_path = &service_attrs.crate_path;
let self_ty = &item_impl.self_ty;
let type_name = extract_type_name(self_ty).unwrap_or_else(|| "Service".to_string());
let method_call_name = format_ident!("__{}MethodCall", type_name);
let user_methods_name = format_ident!("__{}UserMethods", type_name);
let reply_params_name = format_ident!("__{}ReplyParams", type_name);
let reply_error_name = format_ident!("__{}ReplyError", type_name);
let reply_stream_params_name = format_ident!("__{}ReplyStreamParams", type_name);
let reply_stream_name = format_ident!("__{}ReplyStream", type_name);
let interfaces = collect_interfaces(methods_info);
let method_call_enum = generate_method_call_enum(
methods_info,
&method_call_name,
&user_methods_name,
crate_path,
)?;
let reply_params_enum = generate_reply_params_enum(
methods_info,
&method_call_name,
&reply_params_name,
crate_path,
)?;
let (reply_error_enum, error_type_map) =
generate_reply_error_enum(methods_info, &reply_error_name, crate_path);
let error_type: syn::Type = syn::parse_quote!(#reply_error_name<'ser>);
let interface_descriptions = generate_interface_descriptions(
methods_info,
service_attrs,
&interfaces,
crate_path,
&type_name,
);
let (reply_stream_params_enum, stream_item_type_map) =
generate_reply_stream_params_enum(methods_info, &reply_stream_params_name);
let needs_stream_boxing = methods_info
.iter()
.any(|m| m.is_streaming && m.stream_uses_impl_trait);
let handle_body_ctx = HandleBodyContext {
crate_path,
method_call_name: &method_call_name,
user_methods_name: &user_methods_name,
reply_params_name: &reply_params_name,
reply_error_name: &reply_error_name,
reply_stream_params_name: &reply_stream_params_name,
reply_stream_name: &reply_stream_name,
error_type_map: &error_type_map,
stream_item_type_map: &stream_item_type_map,
interfaces: &interfaces,
type_name: &type_name,
needs_stream_boxing,
};
let handle_body = generate_handle_body(methods_info, service_attrs, &handle_body_ctx)?;
let (socket_ty, generics, user_where_clause) = item_impl
.generics
.params
.iter()
.find_map(|p| match p {
GenericParam::Type(ty) => Some((
ty.ident.clone(),
item_impl.generics.clone(),
item_impl.generics.where_clause.clone(),
)),
_ => None,
})
.unwrap_or_else(|| {
let default_ident: Ident = format_ident!("__ZlinkSock");
let mut generics = syn::Generics::default();
generics
.params
.push(GenericParam::Type(syn::TypeParam::from(
default_ident.clone(),
)));
(default_ident, generics, None)
});
let user_predicates = user_where_clause.map(|w| w.predicates).unwrap_or_default();
let where_clause = quote! {
where
#socket_ty: #crate_path::connection::Socket,
#user_predicates
};
let has_streaming_methods = methods_info.iter().any(|m| m.is_streaming);
let (reply_stream_type, reply_stream_params_type, reply_stream_enum) = if has_streaming_methods
{
if needs_stream_boxing {
(
quote! {
::std::boxed::Box<
dyn #crate_path::futures_util::Stream<
Item = #crate_path::service::ReplyStreamItem<#reply_stream_params_name>
> + ::core::marker::Unpin
>
},
quote! { #reply_stream_params_name },
quote! {},
)
} else {
let reply_stream_enum = generate_reply_stream_enum(
methods_info,
&reply_stream_name,
&reply_stream_params_name,
&stream_item_type_map,
crate_path,
);
(
quote! { #reply_stream_name },
quote! { #reply_stream_params_name },
reply_stream_enum,
)
}
} else {
(
quote! { #crate_path::futures_util::stream::Empty<#crate_path::service::ReplyStreamItem<()>> },
quote! { () },
quote! {},
)
};
#[cfg(feature = "std")]
let handle_fds_param = quote! { __zlink_fds: ::std::vec::Vec<::std::os::fd::OwnedFd>, };
#[cfg(not(feature = "std"))]
let handle_fds_param = quote! {};
let service_impl = quote! {
impl #generics #crate_path::Service<#socket_ty> for #self_ty
#where_clause
{
type MethodCall<'de> = #method_call_name<'de>;
type ReplyParams<'ser> = #reply_params_name<'ser> where Self: 'ser;
type ReplyStream = #reply_stream_type;
type ReplyStreamParams = #reply_stream_params_type;
type ReplyError<'ser> = #error_type where Self: 'ser;
async fn handle<'__zlink_ser>(
&'__zlink_ser mut self,
__zlink_call: &'__zlink_ser #crate_path::Call<Self::MethodCall<'_>>,
__zlink_conn: &mut #crate_path::Connection<#socket_ty>,
#handle_fds_param
) -> #crate_path::service::HandleResult<
Self::ReplyParams<'__zlink_ser>,
Self::ReplyStream,
Self::ReplyError<'__zlink_ser>,
> {
#handle_body
}
}
};
Ok(quote! {
#interface_descriptions
#method_call_enum
#reply_params_enum
#reply_stream_params_enum
#reply_stream_enum
#reply_error_enum
#service_impl
})
}
fn generate_method_call_enum(
methods_info: &[MethodInfo],
enum_name: &Ident,
user_methods_name: &Ident,
crate_path: &TokenStream,
) -> Result<TokenStream, Error> {
let variants: Vec<TokenStream> = methods_info
.iter()
.filter_map(|method| {
let full_path = method.full_method_path()?;
let variant_name = format_ident!("{}", method.varlink_name);
let serialized_params: Vec<_> = method.serialized_params().collect();
let fields = if serialized_params.is_empty() {
quote! {}
} else {
let field_defs: Vec<TokenStream> = serialized_params
.iter()
.map(|param| {
let name = ¶m.name;
let converted_ty = convert_type_lifetimes(¶m.ty, "'__de");
let serde_attr = if let Some(ref renamed) = param.serialized_name {
quote! { #[serde(rename = #renamed)] }
} else {
quote! {}
};
quote! {
#serde_attr
#name: #converted_ty
}
})
.collect();
quote! {
{ #(#field_defs),* }
}
};
Some(quote! {
#[serde(rename = #full_path)]
#variant_name #fields
})
})
.collect();
let unused_variant = format_ident!("__{}Unused", user_methods_name);
let unknown_variant = format_ident!("__{}Unknown", user_methods_name);
let user_methods_enum = if variants.is_empty() {
quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Deserialize)]
#[serde(tag = "method", content = "parameters")]
pub enum #user_methods_name<'__de> {
#unused_variant(::core::marker::PhantomData<&'__de ()>),
}
}
} else {
quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Deserialize)]
#[serde(tag = "method", content = "parameters")]
pub enum #user_methods_name<'__de> {
#unused_variant(::core::marker::PhantomData<&'__de ()>),
#(#variants,)*
#[serde(other)]
#unknown_variant,
}
}
};
let outer_enum = quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Deserialize)]
#[serde(untagged)]
pub enum #enum_name<'__de> {
#[serde(borrow)]
__VarlinkService(#crate_path::varlink_service::Method<'__de>),
__UserMethods(#user_methods_name<'__de>),
}
};
Ok(quote! {
#user_methods_enum
#outer_enum
})
}
fn build_return_type_variant_map(methods_info: &[MethodInfo]) -> HashMap<String, usize> {
let mut type_to_variant: HashMap<String, usize> = HashMap::new();
let mut variant_idx = 0;
for method in methods_info {
let Some(ref return_type) = method.return_type else {
continue;
};
let type_str = return_type.to_token_stream().to_string();
if let std::collections::hash_map::Entry::Vacant(e) = type_to_variant.entry(type_str) {
e.insert(variant_idx);
variant_idx += 1;
}
}
type_to_variant
}
fn build_error_type_variant_map(methods_info: &[MethodInfo]) -> HashMap<String, usize> {
let mut type_to_variant: HashMap<String, usize> = HashMap::new();
let mut variant_idx = 0;
for method in methods_info {
let Some(ref error_type) = method.error_type else {
continue;
};
let type_str = error_type.to_token_stream().to_string();
if let std::collections::hash_map::Entry::Vacant(e) = type_to_variant.entry(type_str) {
e.insert(variant_idx);
variant_idx += 1;
}
}
type_to_variant
}
fn build_stream_item_type_variant_map(methods_info: &[MethodInfo]) -> HashMap<String, Ident> {
let mut type_to_variant: HashMap<String, Ident> = HashMap::new();
for method in methods_info {
if !method.is_streaming {
continue;
}
let Some(ref stream_item_type) = method.stream_item_type else {
continue;
};
let type_str = stream_item_type.to_token_stream().to_string();
if let std::collections::hash_map::Entry::Vacant(e) = type_to_variant.entry(type_str) {
let variant_name = extract_type_name(stream_item_type)
.map(|name| format_ident!("{}", name))
.unwrap_or_else(|| format_ident!("__Unknown"));
e.insert(variant_name);
}
}
type_to_variant
}
fn generate_reply_error_enum(
methods_info: &[MethodInfo],
enum_name: &Ident,
crate_path: &TokenStream,
) -> (TokenStream, HashMap<String, usize>) {
let error_type_map = build_error_type_variant_map(methods_info);
let mut type_variant_pairs: Vec<_> = error_type_map.iter().collect();
type_variant_pairs.sort_by_key(|(_, idx)| *idx);
let mut variants: Vec<TokenStream> = Vec::new();
let mut from_impls: Vec<TokenStream> = Vec::new();
for (type_str, idx) in type_variant_pairs {
for method in methods_info {
let Some(ref error_type) = method.error_type else {
continue;
};
if &error_type.to_token_stream().to_string() == type_str {
let variant_name = format_ident!("__{}Variant{}", enum_name, idx);
let converted = convert_type_lifetimes(error_type, "'__ser");
variants.push(quote! {
#variant_name(#converted)
});
from_impls.push(quote! {
impl<'__ser> ::core::convert::From<#converted> for #enum_name<'__ser> {
fn from(e: #converted) -> Self {
#enum_name::#variant_name(e)
}
}
});
break;
}
}
}
let varlink_error_variant = format_ident!("__{}VarlinkService", enum_name);
let enum_def = quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Serialize)]
#[serde(untagged)]
pub enum #enum_name<'__ser> {
#varlink_error_variant(#crate_path::varlink_service::Error<'__ser>),
#(#variants,)*
}
impl<'__ser> ::core::convert::From<#crate_path::varlink_service::Error<'__ser>>
for #enum_name<'__ser>
{
fn from(e: #crate_path::varlink_service::Error<'__ser>) -> Self {
#enum_name::#varlink_error_variant(e)
}
}
#(#from_impls)*
};
(enum_def, error_type_map)
}
fn generate_reply_stream_params_enum(
methods_info: &[MethodInfo],
enum_name: &Ident,
) -> (TokenStream, HashMap<String, Ident>) {
let type_map = build_stream_item_type_variant_map(methods_info);
let mut variants: Vec<TokenStream> = Vec::new();
let mut from_impls: Vec<TokenStream> = Vec::new();
for (type_str, variant_name) in &type_map {
for method in methods_info {
if !method.is_streaming {
continue;
}
let Some(ref item_type) = method.stream_item_type else {
continue;
};
if &item_type.to_token_stream().to_string() == type_str {
variants.push(quote! {
#variant_name(#item_type)
});
from_impls.push(quote! {
impl ::core::convert::From<#item_type> for #enum_name {
fn from(v: #item_type) -> Self {
#enum_name::#variant_name(v)
}
}
});
break;
}
}
}
if variants.is_empty() {
let enum_def = quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Serialize)]
#[serde(untagged)]
pub enum #enum_name {
__Unused(()),
}
};
return (enum_def, type_map);
}
let enum_def = quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Serialize)]
#[serde(untagged)]
pub enum #enum_name {
#(#variants,)*
}
#(#from_impls)*
};
(enum_def, type_map)
}
fn generate_reply_stream_enum(
methods_info: &[MethodInfo],
enum_name: &Ident,
reply_stream_params_name: &Ident,
stream_item_type_map: &HashMap<String, Ident>,
crate_path: &TokenStream,
) -> TokenStream {
let projection_name = format_ident!("{}Proj", enum_name);
let streaming_methods: Vec<_> = methods_info
.iter()
.filter(|m| m.is_streaming && m.stream_return_type.is_some())
.collect();
if streaming_methods.is_empty() {
return quote! {};
}
let variants: Vec<TokenStream> = streaming_methods
.iter()
.map(|method| {
let variant_name = format_ident!("{}", method.varlink_name);
let stream_type = method.stream_return_type.as_ref().unwrap();
quote! {
#variant_name { #[pin] stream: #stream_type }
}
})
.collect();
#[cfg(feature = "std")]
let poll_arms: Vec<TokenStream> = streaming_methods
.iter()
.map(|method| {
let variant_name = format_ident!("{}", method.varlink_name);
let item_type = method.stream_item_type.as_ref().unwrap();
let type_str = item_type.to_token_stream().to_string();
let params_variant = stream_item_type_map
.get(&type_str)
.cloned()
.unwrap_or_else(|| format_ident!("__Unknown"));
if method.return_fds {
quote! {
#projection_name::#variant_name { stream } => {
stream.poll_next(cx).map(|opt| opt.map(|(reply, fds)| {
let mapped_reply = reply.map(|params| {
#reply_stream_params_name::#params_variant(params)
});
(mapped_reply, fds)
}))
}
}
} else {
quote! {
#projection_name::#variant_name { stream } => {
stream.poll_next(cx).map(|opt| opt.map(|reply| {
let mapped_reply = reply.map(|params| {
#reply_stream_params_name::#params_variant(params)
});
(mapped_reply, ::std::vec::Vec::new())
}))
}
}
}
})
.collect();
#[cfg(not(feature = "std"))]
let poll_arms: Vec<TokenStream> = streaming_methods
.iter()
.map(|method| {
let variant_name = format_ident!("{}", method.varlink_name);
let item_type = method.stream_item_type.as_ref().unwrap();
let type_str = item_type.to_token_stream().to_string();
let params_variant = stream_item_type_map
.get(&type_str)
.cloned()
.unwrap_or_else(|| format_ident!("__Unknown"));
quote! {
#projection_name::#variant_name { stream } => {
stream.poll_next(cx).map(|opt| opt.map(|reply| {
reply.map(|params| #reply_stream_params_name::#params_variant(params))
}))
}
}
})
.collect();
quote! {
#crate_path::pin_project_lite::pin_project! {
#[allow(private_interfaces)]
#[project = #projection_name]
pub enum #enum_name {
#(#variants,)*
}
}
impl #crate_path::futures_util::Stream for #enum_name {
type Item = #crate_path::service::ReplyStreamItem<#reply_stream_params_name>;
fn poll_next(
self: ::core::pin::Pin<&mut Self>,
cx: &mut ::core::task::Context<'_>,
) -> ::core::task::Poll<::core::option::Option<Self::Item>> {
match self.project() {
#(#poll_arms)*
}
}
}
}
}
fn generate_reply_params_enum(
methods_info: &[MethodInfo],
method_call_name: &Ident,
enum_name: &Ident,
crate_path: &TokenStream,
) -> Result<TokenStream, Error> {
let mut variants: Vec<TokenStream> = Vec::new();
let type_to_variant = build_return_type_variant_map(methods_info);
let mut type_variant_pairs: Vec<_> = type_to_variant.iter().collect();
type_variant_pairs.sort_by_key(|(_, idx)| *idx);
for (type_str, idx) in type_variant_pairs {
for method in methods_info {
let Some(ref return_type) = method.return_type else {
continue;
};
if &return_type.to_token_stream().to_string() == type_str {
let variant_name = format_ident!("__{}Variant{}", method_call_name, idx);
let converted = convert_type_lifetimes(return_type, "'__ser");
variants.push(quote! {
#variant_name(#converted)
});
break;
}
}
}
let unused_variant = format_ident!("__{}Unused", enum_name);
let varlink_reply_variant = format_ident!("__{}VarlinkService", enum_name);
if variants.is_empty() {
return Ok(quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Serialize)]
#[serde(untagged)]
pub enum #enum_name<'__ser> {
#varlink_reply_variant(#crate_path::varlink_service::Reply<'__ser>),
#unused_variant(::core::marker::PhantomData<&'__ser ()>),
}
});
}
Ok(quote! {
#[allow(private_interfaces)]
#[derive(::core::fmt::Debug, ::serde::Serialize)]
#[serde(untagged)]
pub enum #enum_name<'__ser> {
#varlink_reply_variant(#crate_path::varlink_service::Reply<'__ser>),
#(#variants,)*
#unused_variant(::core::marker::PhantomData<&'__ser ()>),
}
})
}
fn generate_interface_descriptions(
methods_info: &[MethodInfo],
service_attrs: &ServiceAttrs,
interfaces: &[String],
crate_path: &TokenStream,
type_name: &str,
) -> TokenStream {
let mut descriptions: Vec<TokenStream> = Vec::new();
for interface in interfaces {
let const_name = format_ident!(
"__{}_INTERFACE_{}",
type_name.to_uppercase(),
interface.replace('.', "_").to_uppercase()
);
let interface_methods: Vec<&MethodInfo> = methods_info
.iter()
.filter(|m| m.interface.as_ref() == Some(interface))
.collect();
let method_consts: Vec<TokenStream> = interface_methods
.iter()
.enumerate()
.map(|(idx, method)| {
let method_const_name = format_ident!("__METHOD_{}", idx);
let method_name = &method.varlink_name;
let in_params: Vec<TokenStream> = method
.serialized_params()
.map(|p| {
let default_name = p.name.to_string().trim_start_matches('_').to_string();
let param_name = p.serialized_name.as_ref().unwrap_or(&default_name);
let ty = convert_type_lifetimes(&p.ty, "'static");
quote! {
&#crate_path::idl::Parameter::new(
#param_name,
<#ty as #crate_path::introspect::Type>::TYPE,
&[],
)
}
})
.collect();
let in_params_const = if in_params.is_empty() {
quote! {
const __IN_PARAMS: &[&#crate_path::idl::Parameter<'static>] = &[];
}
} else {
quote! {
const __IN_PARAMS: &[&#crate_path::idl::Parameter<'static>] =
&[#(#in_params),*];
}
};
quote! {
const #method_const_name: &#crate_path::idl::Method<'static> = &{
#in_params_const
#crate_path::idl::Method::new(
#method_name,
__IN_PARAMS,
&[],
&[],
)
};
}
})
.collect();
let method_refs: Vec<TokenStream> = (0..interface_methods.len())
.map(|idx| {
let method_const_name = format_ident!("__METHOD_{}", idx);
quote! { #method_const_name }
})
.collect();
let custom_types: Vec<TokenStream> = service_attrs
.custom_types
.iter()
.map(|ty| {
quote! {
<#ty as #crate_path::introspect::CustomType>::CUSTOM_TYPE
}
})
.collect();
let mut seen_error_types = HashSet::new();
let error_types: Vec<TokenStream> = methods_info
.iter()
.filter(|m| m.interface.as_ref() == Some(interface))
.filter_map(|m| m.error_type.as_ref())
.filter(|err_ty| {
let type_str = err_ty.to_token_stream().to_string();
seen_error_types.insert(type_str)
})
.map(|err_ty| {
let err_ty = convert_type_lifetimes(err_ty, "'static");
quote! {
<#err_ty as #crate_path::introspect::ReplyError>::VARIANTS
}
})
.collect();
let error_variants_expr = if error_types.is_empty() {
quote! { &[] }
} else {
let first_err = &error_types[0];
quote! { #first_err }
};
descriptions.push(quote! {
#[doc(hidden)]
const #const_name: &#crate_path::idl::Interface<'static> = &{
#(#method_consts)*
#crate_path::idl::Interface::new(
#interface,
&[#(#method_refs),*],
&[#(#custom_types),*],
#error_variants_expr,
&[],
)
};
});
}
quote! { #(#descriptions)* }
}
fn wrap_handle_result_no_fds(inner: TokenStream) -> TokenStream {
#[cfg(feature = "std")]
{
quote! {
{
let __method_reply = { #inner };
(__method_reply, ::std::vec::Vec::new())
}
}
}
#[cfg(not(feature = "std"))]
{
inner
}
}
#[cfg(feature = "std")]
fn wrap_handle_result_with_fds(inner: TokenStream, fds_expr: TokenStream) -> TokenStream {
quote! {
{
let __method_reply = { #inner };
(__method_reply, #fds_expr)
}
}
}
#[cfg(not(feature = "std"))]
fn wrap_handle_result_with_fds(inner: TokenStream, _fds_expr: TokenStream) -> TokenStream {
inner
}
fn generate_handle_body(
methods_info: &[MethodInfo],
service_attrs: &ServiceAttrs,
ctx: &HandleBodyContext<'_>,
) -> Result<TokenStream, Error> {
let HandleBodyContext {
crate_path,
method_call_name,
user_methods_name,
reply_params_name,
reply_error_name,
reply_stream_params_name,
reply_stream_name,
error_type_map,
stream_item_type_map,
interfaces,
type_name,
needs_stream_boxing,
} = ctx;
let mut user_match_arms: Vec<TokenStream> = Vec::new();
let type_to_variant = build_return_type_variant_map(methods_info);
for method in methods_info {
let Some(_full_path) = method.full_method_path() else {
continue;
};
let enum_variant_name = format_ident!("{}", method.varlink_name);
let method_name = &method.name;
let serialized_params: Vec<_> = method.serialized_params().collect();
let pattern = if serialized_params.is_empty() {
quote! { #user_methods_name::#enum_variant_name }
} else {
let param_names: Vec<_> = serialized_params.iter().map(|p| &p.name).collect();
quote! { #user_methods_name::#enum_variant_name { #(#param_names),* } }
};
let method_call = if method.has_connection_param() {
let body = &method.body;
let conn_bindings: Vec<TokenStream> = method
.params
.iter()
.filter(|p| p.is_connection)
.map(|p| {
let name = &p.name;
quote! { let #name = __zlink_conn; }
})
.collect();
let more_bindings: Vec<TokenStream> = method
.params
.iter()
.filter(|p| p.is_more)
.map(|p| {
let name = &p.name;
quote! { let #name = __zlink_call.more(); }
})
.collect();
let fds_bindings: Vec<TokenStream> = method
.params
.iter()
.filter(|p| p.is_fds)
.map(|p| {
let name = &p.name;
quote! { let #name = __zlink_fds; }
})
.collect();
let param_bindings: Vec<TokenStream> = method
.params
.iter()
.filter(|p| !p.is_connection && !p.is_more && !p.is_fds)
.map(|p| {
let name = &p.name;
quote! { let #name = ::core::clone::Clone::clone(#name); }
})
.collect();
quote! {
{
#(#conn_bindings)*
#(#more_bindings)*
#(#fds_bindings)*
#(#param_bindings)*
async move #body
}.await
}
} else {
let call_args: Vec<TokenStream> = method
.params
.iter()
.map(|p| {
if p.is_more {
quote! { __zlink_call.more() }
} else if p.is_fds {
quote! { __zlink_fds }
} else {
let name = &p.name;
quote! { ::core::clone::Clone::clone(#name) }
}
})
.collect();
quote! { self.#method_name(#(#call_args),*).await }
};
let return_expr = if method.is_streaming {
let item_type = method
.stream_item_type
.as_ref()
.expect("streaming method must have stream_item_type");
let type_str = item_type.to_token_stream().to_string();
let stream_variant_name = stream_item_type_map
.get(&type_str)
.cloned()
.unwrap_or_else(|| format_ident!("__Unknown"));
let method_variant_name = format_ident!("{}", method.varlink_name);
let streaming_reply = if *needs_stream_boxing {
#[cfg(feature = "std")]
let boxed_stream = if method.return_fds {
quote! {
let __stream = #method_call;
let __mapped = #crate_path::futures_util::StreamExt::map(
__stream,
|(__reply, __fds)| {
let __mapped_reply = __reply.map(|__params| {
#reply_stream_params_name::#stream_variant_name(__params)
});
(__mapped_reply, __fds)
},
);
let __boxed: ::std::boxed::Box<
dyn #crate_path::futures_util::Stream<
Item = #crate_path::service::ReplyStreamItem<
#reply_stream_params_name
>
> + ::core::marker::Unpin
> = ::std::boxed::Box::new(__mapped);
#crate_path::service::MethodReply::Multi(__boxed)
}
} else {
quote! {
let __stream = #method_call;
let __mapped = #crate_path::futures_util::StreamExt::map(__stream, |__reply| {
let __mapped_reply = __reply.map(|__params| {
#reply_stream_params_name::#stream_variant_name(__params)
});
(__mapped_reply, ::std::vec::Vec::new())
});
let __boxed: ::std::boxed::Box<
dyn #crate_path::futures_util::Stream<
Item = #crate_path::service::ReplyStreamItem<
#reply_stream_params_name
>
> + ::core::marker::Unpin
> = ::std::boxed::Box::new(__mapped);
#crate_path::service::MethodReply::Multi(__boxed)
}
};
#[cfg(not(feature = "std"))]
let boxed_stream = quote! {
let __stream = #method_call;
let __mapped = #crate_path::futures_util::StreamExt::map(__stream, |__reply| {
__reply.map(|__params| {
#reply_stream_params_name::#stream_variant_name(__params)
})
});
let __boxed: ::std::boxed::Box<
dyn #crate_path::futures_util::Stream<
Item = #crate_path::service::ReplyStreamItem<
#reply_stream_params_name
>
> + ::core::marker::Unpin
> = ::std::boxed::Box::new(__mapped);
#crate_path::service::MethodReply::Multi(__boxed)
};
boxed_stream
} else {
quote! {
let __stream = #method_call;
#crate_path::service::MethodReply::Multi(
#reply_stream_name::#method_variant_name { stream: __stream }
)
}
};
wrap_handle_result_no_fds(streaming_reply)
} else if method.return_fds && method.returns_result {
let error_variant = method.error_type.as_ref().map(|err_ty| {
let type_str = err_ty.to_token_stream().to_string();
let variant_idx = error_type_map.get(&type_str).copied().unwrap_or(0);
format_ident!("__{}Variant{}", reply_error_name, variant_idx)
});
let error_convert = if let Some(ref err_variant) = error_variant {
quote! { #reply_error_name::#err_variant(__err) }
} else {
quote! { ::core::convert::From::from(__err) }
};
let err_reply = quote! {
#crate_path::service::MethodReply::Error(#error_convert)
};
let err_arm = wrap_handle_result_with_fds(err_reply, quote! { __out_fds });
if let Some(ref return_type) = method.return_type {
let type_str = return_type.to_token_stream().to_string();
let variant_idx = type_to_variant.get(&type_str).copied().unwrap_or(0);
let reply_variant_name =
format_ident!("__{}Variant{}", method_call_name, variant_idx);
let ok_reply = quote! {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#reply_variant_name(__ok)
))
};
let ok_arm = wrap_handle_result_with_fds(ok_reply, quote! { __out_fds });
quote! {
let (__result, __out_fds) = #method_call;
match __result {
::core::result::Result::Ok(__ok) => {
#ok_arm
}
::core::result::Result::Err(__err) => {
#err_arm
}
}
}
} else {
let ok_reply = quote! {
#crate_path::service::MethodReply::Single(None)
};
let ok_arm = wrap_handle_result_with_fds(ok_reply, quote! { __out_fds });
quote! {
let (__result, __out_fds) = #method_call;
match __result {
::core::result::Result::Ok(()) => {
#ok_arm
}
::core::result::Result::Err(__err) => {
#err_arm
}
}
}
}
} else if method.return_fds {
if let Some(ref return_type) = method.return_type {
let type_str = return_type.to_token_stream().to_string();
let variant_idx = type_to_variant.get(&type_str).copied().unwrap_or(0);
let reply_variant_name =
format_ident!("__{}Variant{}", method_call_name, variant_idx);
let ok_reply = quote! {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#reply_variant_name(__ok)
))
};
let ok_arm = wrap_handle_result_with_fds(ok_reply, quote! { __out_fds });
quote! {
let (__ok, __out_fds) = #method_call;
#ok_arm
}
} else {
let ok_reply = quote! {
#crate_path::service::MethodReply::Single(None)
};
let ok_arm = wrap_handle_result_with_fds(ok_reply, quote! { __out_fds });
quote! {
let ((), __out_fds) = #method_call;
#ok_arm
}
}
} else if method.returns_result {
let error_variant = method.error_type.as_ref().map(|err_ty| {
let type_str = err_ty.to_token_stream().to_string();
let variant_idx = error_type_map.get(&type_str).copied().unwrap_or(0);
format_ident!("__{}Variant{}", reply_error_name, variant_idx)
});
if let Some(ref return_type) = method.return_type {
let type_str = return_type.to_token_stream().to_string();
let variant_idx = type_to_variant.get(&type_str).copied().unwrap_or(0);
let reply_variant_name =
format_ident!("__{}Variant{}", method_call_name, variant_idx);
let error_convert = if let Some(err_variant) = error_variant {
quote! { #reply_error_name::#err_variant(__err) }
} else {
quote! { ::core::convert::From::from(__err) }
};
wrap_handle_result_no_fds(quote! {
match #method_call {
::core::result::Result::Ok(__ok) => {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#reply_variant_name(__ok)
))
}
::core::result::Result::Err(__err) => {
#crate_path::service::MethodReply::Error(#error_convert)
}
}
})
} else {
let error_convert = if let Some(err_variant) = error_variant {
quote! { #reply_error_name::#err_variant(__err) }
} else {
quote! { ::core::convert::From::from(__err) }
};
wrap_handle_result_no_fds(quote! {
match #method_call {
::core::result::Result::Ok(()) => {
#crate_path::service::MethodReply::Single(None)
}
::core::result::Result::Err(__err) => {
#crate_path::service::MethodReply::Error(#error_convert)
}
}
})
}
} else if let Some(ref return_type) = method.return_type {
let type_str = return_type.to_token_stream().to_string();
let variant_idx = type_to_variant.get(&type_str).copied().unwrap_or(0);
let reply_variant_name = format_ident!("__{}Variant{}", method_call_name, variant_idx);
wrap_handle_result_no_fds(quote! {
let __result = #method_call;
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#reply_variant_name(__result)
))
})
} else {
wrap_handle_result_no_fds(quote! {
let _ = #method_call;
#crate_path::service::MethodReply::Single(None)
})
};
user_match_arms.push(quote! {
#pattern => {
#return_expr
}
});
}
let unused_variant = format_ident!("__{}Unused", user_methods_name);
let unknown_variant = format_ident!("__{}Unknown", user_methods_name);
let varlink_error_variant = format_ident!("__{}VarlinkService", reply_error_name);
let varlink_reply_variant = format_ident!("__{}VarlinkService", reply_params_name);
user_match_arms.insert(
0,
quote! {
#user_methods_name::#unused_variant(_) => {
unreachable!("unused variant should never be matched")
}
},
);
let unknown_method_reply = wrap_handle_result_no_fds(quote! {
#crate_path::service::MethodReply::Error(
#reply_error_name::#varlink_error_variant(
#crate_path::varlink_service::Error::MethodNotFound {
method: ::std::borrow::Cow::Borrowed("unknown"),
}
)
)
});
user_match_arms.push(quote! {
#user_methods_name::#unknown_variant => {
#unknown_method_reply
}
});
let user_methods_match = quote! {
#method_call_name::__UserMethods(__user_method) => {
match __user_method {
#(#user_match_arms)*
}
}
};
let interface_match_arms: Vec<TokenStream> = interfaces
.iter()
.map(|interface| {
let const_name = format_ident!(
"__{}_INTERFACE_{}",
type_name.to_uppercase(),
interface.replace('.', "_").to_uppercase()
);
let desc_reply = wrap_handle_result_no_fds(quote! {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#varlink_reply_variant(
#crate_path::varlink_service::Reply::InterfaceDescription(desc)
)
))
});
quote! {
#interface => {
let desc =
#crate_path::varlink_service::InterfaceDescription::from(#const_name);
#desc_reply
}
}
})
.collect();
let interfaces_list: Vec<TokenStream> =
interfaces.iter().map(|iface| quote! { #iface }).collect();
let vendor = service_attrs
.vendor
.as_ref()
.map(|v| quote! { #v })
.unwrap_or_else(|| quote! { "" });
let product = service_attrs
.product
.as_ref()
.map(|v| quote! { #v })
.unwrap_or_else(|| quote! { "" });
let version = service_attrs
.version
.as_ref()
.map(|v| quote! { #v })
.unwrap_or_else(|| quote! { "" });
let url = service_attrs
.url
.as_ref()
.map(|v| quote! { #v })
.unwrap_or_else(|| quote! { "" });
let get_info_reply = wrap_handle_result_no_fds(quote! {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#varlink_reply_variant(
#crate_path::varlink_service::Reply::Info(info)
)
))
});
let varlink_desc_reply = wrap_handle_result_no_fds(quote! {
#crate_path::service::MethodReply::Single(Some(
#reply_params_name::#varlink_reply_variant(
#crate_path::varlink_service::Reply::InterfaceDescription(desc)
)
))
});
let interface_not_found_reply = wrap_handle_result_no_fds(quote! {
#crate_path::service::MethodReply::Error(
#reply_error_name::#varlink_error_variant(
#crate_path::varlink_service::Error::InterfaceNotFound {
interface: ::std::borrow::Cow::Borrowed(interface),
}
)
)
});
let varlink_service_match = quote! {
#method_call_name::__VarlinkService(__varlink_method) => {
match __varlink_method {
#crate_path::varlink_service::Method::GetInfo => {
let info = #crate_path::varlink_service::Info::new(
#vendor,
#product,
#version,
#url,
::std::vec![
#(#interfaces_list,)*
#crate_path::varlink_service::INTERFACE_NAME,
],
);
#get_info_reply
}
#crate_path::varlink_service::Method::GetInterfaceDescription { interface } => {
match *interface {
#(#interface_match_arms)*
#crate_path::varlink_service::INTERFACE_NAME => {
let desc =
#crate_path::varlink_service::InterfaceDescription::from(
#crate_path::varlink_service::DESCRIPTION
);
#varlink_desc_reply
}
_ => {
#interface_not_found_reply
}
}
}
}
}
};
let uses_connection = methods_info.iter().any(|m| m.has_connection_param());
let conn_suppression = if uses_connection {
quote! {}
} else {
quote! { let _ = __zlink_conn; }
};
Ok(quote! {
#conn_suppression
match __zlink_call.method() {
#varlink_service_match
#user_methods_match
}
})
}