use darling::FromMeta;
use darling::util::SpannedValue;
use proc_macro2::TokenStream;
use quote::quote;
use syn::Item;
#[derive(Debug, Default, FromMeta)]
pub struct ContractExceptionArgs {
pub version: Option<SpannedValue<String>>,
pub name: Option<SpannedValue<String>>,
pub wire_crate: Option<SpannedValue<String>>,
}
pub fn expand(args: ContractExceptionArgs, item: Item) -> TokenStream {
if let Some(version) = &args.version
&& let Err(err) = crate::common::util::validate_version_string(
version.span(),
version,
)
{
return err.into_compile_error();
}
if let Some(name) = &args.name
&& let Err(err) =
crate::common::util::validate_spec_name(name.span(), name)
{
return err.into_compile_error();
}
if let Some(wire_crate) = &args.wire_crate
&& let Err(err) = crate::common::util::validate_wire_crate_name(
wire_crate.span(),
wire_crate,
)
{
return err.into_compile_error();
}
let generics = match &item {
Item::Enum(e) => Some(&e.generics),
Item::Struct(s) => Some(&s.generics),
_ => None,
};
if let Some(generics) = generics
&& let Err(err) = crate::common::util::validate_no_generics(
generics,
false,
"#[contract_exception] types",
)
{
return err.into_compile_error();
}
match item {
Item::Enum(e) => expand_enum(args, e),
Item::Struct(s) => expand_struct(args, s),
other => syn::Error::new_spanned(
other,
"#[contract_exception] is only supported on enums and structs",
)
.into_compile_error(),
}
}
fn expand_contract_type_name(
ident: &syn::Ident,
args: &ContractExceptionArgs,
) -> TokenStream {
crate::contract_type_name_impl(
&crate::common::contract::sdk_crate_path(),
ident,
args.name.as_ref().map(|name| name.as_str()),
args.version.as_ref().map(|version| version.as_str()),
)
}
fn expand_enum(
args: ContractExceptionArgs,
item: syn::ItemEnum,
) -> TokenStream {
if let Err(err) = crate::check_enum_discriminants(&item.variants) {
return err.into_compile_error();
}
let ident = &item.ident;
let ident_string = ident.to_string();
let type_name = args.name.as_deref().unwrap_or(&ident_string);
let selector = compute_selector(&args, type_name);
let name_impl = expand_contract_type_name(ident, &args);
quote! {
#[derive(Debug, PartialEq, ::thruput_sdk::ReadXDR, ::thruput_sdk::WriteXDR, ::thruput_sdk::ContractTypeSpec)]
#[xdrs(crate = "::thruput_sdk::__xdrs")]
#[repr(i32)]
#item
impl ::thruput_sdk::exn::Exn for #ident {
const SELECTOR: u64 = #selector;
}
#name_impl
}
}
fn expand_struct(
args: ContractExceptionArgs,
item: syn::ItemStruct,
) -> TokenStream {
let ident = &item.ident;
let ident_string = ident.to_string();
let type_name = args.name.as_deref().unwrap_or(&ident_string);
let selector = compute_selector(&args, type_name);
let name_impl = expand_contract_type_name(ident, &args);
quote! {
#[derive(Debug, PartialEq, ::thruput_sdk::ReadXDR, ::thruput_sdk::WriteXDR, ::thruput_sdk::ContractTypeSpec)]
#[xdrs(crate = "::thruput_sdk::__xdrs")]
#item
impl ::thruput_sdk::exn::Exn for #ident {
const SELECTOR: u64 = #selector;
}
#name_impl
}
}
fn compute_selector(args: &ContractExceptionArgs, type_name: &str) -> u64 {
let version = args
.version
.as_ref()
.map(|v| v.as_str().to_string())
.unwrap_or_else(|| {
std::env::var("CARGO_PKG_VERSION")
.unwrap_or_else(|_| "0.0.0".to_string())
});
let crate_name = args
.wire_crate
.as_ref()
.map(|name| name.to_string())
.unwrap_or_else(|| {
std::env::var("CARGO_PKG_NAME")
.unwrap_or_else(|_| "_unknown_".to_string())
});
thruput_contract_util::exception_selector(&version, &crate_name, type_name)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_invalid_wire_crate_is_rejected_at_the_attribute() {
let args = ContractExceptionArgs {
wire_crate: Some(SpannedValue::new(
"other::package".into(),
proc_macro2::Span::call_site(),
)),
..Default::default()
};
let output =
expand(args, syn::parse_quote! { enum Error { Denied = 1 } });
assert!(output.to_string().contains("wire_crate must be"));
}
}