use super::{util, Context};
use crate::contract::common::{expand_data_struct, expand_data_tuple, expand_params};
use ethers_core::{
abi::{ethabi::AbiError, ErrorExt},
macros::{ethers_contract_crate, ethers_core_crate},
};
use eyre::Result;
use inflector::Inflector;
use proc_macro2::{Ident, TokenStream};
use quote::quote;
impl Context {
pub(crate) fn errors(&self) -> Result<TokenStream> {
let data_types = self
.abi
.errors
.values()
.flatten()
.map(|event| self.expand_error(event))
.collect::<Result<Vec<_>>>()?;
let errors_enum_decl = if self.abi.errors.values().flatten().count() > 1 {
self.expand_errors_enum()
} else {
quote! {}
};
Ok(quote! {
#( #data_types )*
#errors_enum_decl
})
}
fn expand_error(&self, error: &AbiError) -> Result<TokenStream> {
let sig = self.error_aliases.get(&error.abi_signature()).cloned();
let abi_signature = error.abi_signature();
let error_name = error_struct_name(&error.name, sig);
let fields = self.expand_error_params(error)?;
let all_anonymous_fields = error.inputs.iter().all(|input| input.name.is_empty());
let data_type_definition = if all_anonymous_fields {
expand_data_tuple(&error_name, &fields)
} else {
expand_data_struct(&error_name, &fields)
};
let doc = format!(
"Custom Error type `{}` with signature `{}` and selector `{:?}`",
error.name,
abi_signature,
error.selector()
);
let abi_signature_doc = util::expand_doc(&doc);
let ethers_contract = ethers_contract_crate();
let derives = util::expand_derives(&self.event_derives);
let error_name = &error.name;
Ok(quote! {
#abi_signature_doc
#[derive(Clone, Debug, Default, Eq, PartialEq, #ethers_contract::EthError, #ethers_contract::EthDisplay, #derives)]
#[etherror( name = #error_name, abi = #abi_signature )]
pub #data_type_definition
})
}
fn expand_error_params(&self, error: &AbiError) -> Result<Vec<(TokenStream, TokenStream)>> {
expand_params(&error.inputs, |s| self.internal_structs.get_struct_type(s))
}
fn expand_error_enum_name(&self) -> Ident {
util::ident(&format!("{}Errors", self.contract_ident))
}
fn expand_errors_enum(&self) -> TokenStream {
let variants = self
.abi
.errors
.values()
.flatten()
.map(|err| {
error_struct_name(&err.name, self.error_aliases.get(&err.abi_signature()).cloned())
})
.collect::<Vec<_>>();
let ethers_core = ethers_core_crate();
let ethers_contract = ethers_contract_crate();
let derives = util::expand_derives(&self.event_derives);
let enum_name = self.expand_error_enum_name();
quote! {
#[derive(Debug, Clone, PartialEq, Eq, #ethers_contract::EthAbiType, #derives)]
pub enum #enum_name {
#(#variants(#variants)),*
}
impl #ethers_core::abi::AbiDecode for #enum_name {
fn decode(data: impl AsRef<[u8]>) -> ::std::result::Result<Self, #ethers_core::abi::AbiError> {
#(
if let Ok(decoded) = <#variants as #ethers_core::abi::AbiDecode>::decode(data.as_ref()) {
return Ok(#enum_name::#variants(decoded))
}
)*
Err(#ethers_core::abi::Error::InvalidData.into())
}
}
impl #ethers_core::abi::AbiEncode for #enum_name {
fn encode(self) -> Vec<u8> {
match self {
#(
#enum_name::#variants(element) => element.encode()
),*
}
}
}
impl ::std::fmt::Display for #enum_name {
fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
match self {
#(
#enum_name::#variants(element) => element.fmt(f)
),*
}
}
}
#(
impl ::std::convert::From<#variants> for #enum_name {
fn from(var: #variants) -> Self {
#enum_name::#variants(var)
}
}
)*
}
}
}
fn error_struct_name(error_name: &str, alias: Option<Ident>) -> Ident {
alias.unwrap_or_else(|| util::ident(error_name))
}
pub(crate) fn error_struct_alias(name: &str) -> Ident {
util::ident(&name.to_pascal_case())
}