use std::collections::HashSet;
use crate::visitor::{FindAllParams, FindSubscriptionParams};
use proc_macro2::{Span, TokenStream as TokenStream2};
use proc_macro_crate::{crate_name, FoundCrate};
use quote::quote;
use syn::{parse_quote, punctuated::Punctuated, token::Comma, visit::Visit, Token, WherePredicate};
pub(crate) fn find_jsonrpsee_client_crate() -> Result<proc_macro2::TokenStream, syn::Error> {
find_jsonrpsee_crate("jsonrpsee-http-client", "jsonrpsee-ws-client")
}
pub(crate) fn find_jsonrpsee_server_crate() -> Result<proc_macro2::TokenStream, syn::Error> {
find_jsonrpsee_crate("jsonrpsee-http-server", "jsonrpsee-ws-server")
}
fn find_jsonrpsee_crate(http_name: &str, ws_name: &str) -> Result<proc_macro2::TokenStream, syn::Error> {
match crate_name("jsonrpsee") {
Ok(FoundCrate::Name(name)) => {
let ident = syn::Ident::new(&name, Span::call_site());
Ok(quote!(#ident))
}
Ok(FoundCrate::Itself) => panic!("Deriving RPC methods in any of the `jsonrpsee` crates is not supported"),
Err(_) => match (crate_name(http_name), crate_name(ws_name)) {
(Ok(FoundCrate::Name(name)), _) | (_, Ok(FoundCrate::Name(name))) => {
let ident = syn::Ident::new(&name, Span::call_site());
Ok(quote!(#ident))
}
(Ok(FoundCrate::Itself), _) | (_, Ok(FoundCrate::Itself)) => {
panic!("Deriving RPC methods in any of the `jsonrpsee` crates is not supported")
}
(_, Err(e)) => Err(syn::Error::new(Span::call_site(), &e)),
},
}
}
pub(crate) fn generate_where_clause(
item_trait: &syn::ItemTrait,
sub_tys: &[syn::Type],
is_client: bool,
bounds: Option<&Punctuated<WherePredicate, Comma>>,
) -> Vec<syn::WherePredicate> {
let visitor = visit_trait(item_trait, sub_tys);
let additional_where_clause = item_trait.generics.where_clause.clone();
if let Some(custom_bounds) = bounds {
let mut bounds: Vec<_> = additional_where_clause
.map(|where_clause| where_clause.predicates.into_iter().collect())
.unwrap_or_default();
bounds.extend(custom_bounds.iter().cloned());
return bounds;
}
item_trait
.generics
.type_params()
.map(|ty| {
let ty_path = syn::TypePath { qself: None, path: ty.ident.clone().into() };
let mut bounds: Punctuated<syn::TypeParamBound, Token![+]> = parse_quote!(Send + Sync + 'static);
if is_client {
if visitor.input_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::core::Serialize))
}
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::core::DeserializeOwned))
}
} else {
if visitor.input_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::core::DeserializeOwned))
}
if visitor.ret_params.contains(&ty.ident) || visitor.sub_params.contains(&ty.ident) {
bounds.push(parse_quote!(jsonrpsee::core::Serialize))
}
}
if let Some(where_clause) = &additional_where_clause {
for predicate in where_clause.predicates.iter() {
if let syn::WherePredicate::Type(where_ty) = predicate {
if let syn::Type::Path(ref predicate) = where_ty.bounded_ty {
if *predicate == ty_path {
bounds.extend(where_ty.bounds.clone().into_iter());
}
}
}
}
}
syn::WherePredicate::Type(syn::PredicateType {
lifetimes: None,
bounded_ty: syn::Type::Path(ty_path),
colon_token: <Token![:]>::default(),
bounds,
})
})
.collect()
}
fn visit_trait(item_trait: &syn::ItemTrait, sub_tys: &[syn::Type]) -> FindAllParams {
let type_params: HashSet<_> = item_trait.generics.type_params().map(|t| t.ident.clone()).collect();
let sub_tys = FindSubscriptionParams::new(type_params).visit(sub_tys);
let mut visitor = FindAllParams::new(sub_tys);
visitor.visit_item_trait(item_trait);
visitor
}
pub(crate) fn is_option(ty: &syn::Type) -> bool {
if let syn::Type::Path(path) = ty {
let mut it = path.path.segments.iter().peekable();
while let Some(seg) = it.next() {
if seg.ident == "Option" && it.peek().is_none() {
return true;
}
}
}
false
}
pub(crate) fn extract_doc_comments(attrs: &[syn::Attribute]) -> TokenStream2 {
let docs = attrs.iter().filter(|attr| {
attr.path.is_ident("doc")
&& match attr.parse_meta() {
Ok(syn::Meta::NameValue(meta)) => matches!(&meta.lit, syn::Lit::Str(_)),
_ => false,
}
});
quote! ( #(#docs)* )
}
#[cfg(test)]
mod tests {
use super::is_option;
use syn::parse_quote;
#[test]
fn is_option_works() {
assert!(is_option(&parse_quote!(Option<T>)));
assert!(is_option(&parse_quote!(Option)));
assert!(is_option(&parse_quote!(std::option::Option<R>)));
assert!(!is_option(&parse_quote!(foo::bar::Option::Booyah)));
}
}