use proc_macro::TokenStream;
use proc_macro2::Span;
use quote::{ToTokens, quote};
use syn::{
Expr, ExprLit, FnArg, Ident, ItemFn, Lit, LitStr, Meta, MetaNameValue, PatType, parse_quote,
punctuated::Punctuated,
};
const AT_DOC_SUFFIX: &str = " at specified block";
const AT_SUFFIX: &str = "_at";
const AT_BLOCK_HASH: &str = "Option<H256>";
pub struct StorageQueryBuilder(ItemFn);
impl StorageQueryBuilder {
fn at(&self) -> ItemFn {
let mut at = self.0.clone();
let ident = if let Some(FnArg::Typed(PatType { ty, pat, .. })) =
at.sig.inputs.iter_mut().next_back()
{
*ty = parse_quote! {
impl Into<Option<H256>>
};
Ident::new(&pat.to_token_stream().to_string(), Span::call_site())
} else {
unreachable!("Checked before in function validate");
};
let mut stmts = vec![];
stmts.push(parse_quote! {
let #ident = #ident.into();
});
at.block.stmts = [stmts, at.block.stmts].concat();
at
}
fn latest(&self) -> ItemFn {
let mut latest = self.0.clone();
latest.attrs.iter_mut().for_each(|attr| {
if let Meta::NameValue(MetaNameValue {
value:
Expr::Lit(ExprLit {
attrs: _,
lit: Lit::Str(lit_str),
}),
..
}) = &mut attr.meta
{
*lit_str = LitStr::new(&lit_str.value().replace(AT_DOC_SUFFIX, ""), lit_str.span());
}
});
latest.sig.ident = Ident::new(
latest.sig.ident.to_string().trim_end_matches(AT_SUFFIX),
latest.sig.ident.span(),
);
latest.sig.inputs = Punctuated::from_iter(latest.sig.inputs.into_iter().filter(|v| {
if let FnArg::Typed(PatType { ty, .. }) = v {
return !ty
.to_token_stream()
.to_string()
.replace(' ', "")
.contains(AT_BLOCK_HASH);
}
true
}));
{
let fn_at = &self.0.sig.ident;
let args = latest
.sig
.inputs
.iter()
.filter_map(|v| {
if let FnArg::Typed(PatType { pat, .. }) = v {
Some(Ident::new(
&pat.to_token_stream().to_string(),
Span::call_site(),
))
} else {
None
}
})
.collect::<Vec<Ident>>();
latest.block.stmts = parse_quote! {
self.#fn_at(#(#args,)* None).await
};
}
latest
}
pub fn build(&self) -> TokenStream {
let (at, latest) = (self.at(), self.latest());
quote! {
#at
#latest
}
.into()
}
fn validate(fun: &ItemFn) {
if !fun.attrs.iter().any(|attr| {
attr.path().is_ident("doc")
&& attr
.meta
.require_name_value()
.expect("doc attribute must be name value")
.value
.to_token_stream()
.to_string()
.ends_with(&(AT_DOC_SUFFIX.to_string() + ".\""))
}) {
panic!("the docs must be end with `{AT_DOC_SUFFIX}`");
}
if !fun.sig.ident.to_string().ends_with(AT_SUFFIX) {
panic!("the function name must be end with `_at`");
}
if let Some(FnArg::Typed(PatType { ty, pat, .. })) = fun.sig.inputs.iter().next_back() {
if !pat.to_token_stream().to_string().contains("block_hash") {
panic!("the last argument's name must be `block_hash`");
}
if ty.to_token_stream().to_string().replace(' ', "") != "Option<H256>" {
panic!("the last argument's type must be `Option<H256>`");
};
} else {
panic!("the last argument must be `block_hash: Option<H256>`");
}
}
}
impl From<ItemFn> for StorageQueryBuilder {
fn from(at: ItemFn) -> Self {
Self::validate(&at);
Self(at)
}
}