use proc_macro_error::abort;
use syn::{
Ident, LitBool, LitStr, Path, Token,
parse::{Parse, ParseStream},
punctuated::Punctuated,
};
#[derive(PartialEq, Debug, Default)]
pub(crate) struct ExportArgs {
route: Option<String>,
unwrap_result: bool,
#[cfg(feature = "ethexe")]
payable: bool,
overrides: Option<Path>,
entry_id: Option<u16>,
}
impl ExportArgs {
pub fn route(&self) -> Option<&str> {
self.route.as_deref()
}
pub fn unwrap_result(&self) -> bool {
self.unwrap_result
}
#[cfg(feature = "ethexe")]
pub fn payable(&self) -> bool {
self.payable
}
pub fn overrides(&self) -> Option<&Path> {
self.overrides.as_ref()
}
pub fn entry_id(&self) -> Option<u16> {
self.entry_id
}
}
impl Parse for ExportArgs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
let punctuated: Punctuated<ImportArg, Token![,]> = Punctuated::parse_terminated(input)?;
let mut args = Self {
route: None,
unwrap_result: false,
#[cfg(feature = "ethexe")]
payable: false,
overrides: None,
entry_id: None,
};
for arg in punctuated {
match arg {
ImportArg::Route(route) => {
args.route = Some(route);
}
ImportArg::UnwrapResult(unwrap_result) => {
args.unwrap_result = unwrap_result;
}
#[cfg(feature = "ethexe")]
ImportArg::Payable => {
args.payable = true;
}
ImportArg::Overrides(path) => {
args.overrides = Some(path);
}
ImportArg::EntryId(entry_id) => {
args.entry_id = Some(entry_id);
}
}
}
Ok(args)
}
}
#[derive(Debug)]
enum ImportArg {
Route(String),
UnwrapResult(bool),
#[cfg(feature = "ethexe")]
Payable,
Overrides(Path),
EntryId(u16),
}
impl Parse for ImportArg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let path = input.parse::<Path>()?;
let ident = path.get_ident().unwrap();
match ident.to_string().as_str() {
"route" => {
input.parse::<Token![=]>()?;
if let Ok(lit) = input.parse::<LitStr>() {
let route = lit.value();
_ = syn::parse_str::<Ident>(&route).map_err(|err| {
abort!(
lit.span(),
"`route` argument requires a literal with a valid Rust identifier: {}",
err
)
});
return Ok(Self::Route(route));
}
abort!(ident, "unexpected value for `route` argument: {}", input)
}
"unwrap_result" => {
if input.parse::<Token![=]>().is_ok()
&& let Ok(val) = input.parse::<LitBool>()
{
return Ok(Self::UnwrapResult(val.value()));
}
Ok(Self::UnwrapResult(true))
}
#[cfg(feature = "ethexe")]
"payable" => Ok(Self::Payable),
"overrides" => {
input.parse::<Token![=]>()?;
let path = input.parse::<Path>()?;
Ok(Self::Overrides(path))
}
"entry_id" => {
input.parse::<Token![=]>()?;
let lit = input.parse::<syn::LitInt>()?;
let entry_id = lit.base10_parse::<u16>()?;
Ok(Self::EntryId(entry_id))
}
_ => abort!(ident, "unknown argument: {}", ident),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use quote::quote;
#[test]
fn export_parse_args() {
let input = quote!(route = "CallMe", unwrap_result);
let expected = ExportArgs {
route: Some("CallMe".to_owned()),
unwrap_result: true,
#[cfg(feature = "ethexe")]
payable: false,
overrides: None,
entry_id: None,
};
let args = syn::parse2::<ExportArgs>(input).unwrap();
assert_eq!(expected, args);
}
#[test]
fn export_parse_args_unwrap_result() {
let input = quote!(unwrap_result);
let expected = ExportArgs {
route: None,
unwrap_result: true,
#[cfg(feature = "ethexe")]
payable: false,
overrides: None,
entry_id: None,
};
let args = syn::parse2::<ExportArgs>(input).unwrap();
assert_eq!(expected, args);
}
#[test]
fn export_parse_args_unwrap_result_eq_false() {
let input = quote!(unwrap_result = false);
let expected = ExportArgs {
route: None,
unwrap_result: false,
#[cfg(feature = "ethexe")]
payable: false,
overrides: None,
entry_id: None,
};
let args = syn::parse2::<ExportArgs>(input).unwrap();
assert_eq!(expected, args);
}
#[cfg(feature = "ethexe")]
#[test]
fn export_parse_args_payable() {
let input = quote!(payable);
let expected = ExportArgs {
route: None,
unwrap_result: false,
payable: true,
overrides: None,
entry_id: None,
};
let args = syn::parse2::<ExportArgs>(input).unwrap();
assert_eq!(expected, args);
}
#[test]
fn export_parse_args_overrides() {
let input = quote!(overrides = BaseService, entry_id = 42);
let expected_path: Path = syn::parse2(quote!(BaseService)).unwrap();
let expected = ExportArgs {
route: None,
unwrap_result: false,
#[cfg(feature = "ethexe")]
payable: false,
overrides: Some(expected_path),
entry_id: Some(42),
};
let args = syn::parse2::<ExportArgs>(input).unwrap();
assert_eq!(expected, args);
}
}