obce_codegen/
error.rs

1use darling::FromMeta;
2use itertools::Itertools;
3use proc_macro2::{
4    Ident,
5    TokenStream,
6};
7use quote::quote;
8use syn::{
9    parse2,
10    Error,
11    Expr,
12    ItemEnum,
13};
14
15use crate::{
16    format_err_spanned,
17    types::AttributeArgs,
18    utils::AttributeParser,
19};
20
21fn default_require_ret_val() -> bool {
22    true
23}
24
25#[derive(FromMeta)]
26struct ErrorAttrs {
27    #[darling(default = "default_require_ret_val")]
28    require_ret_val: bool,
29}
30
31#[derive(FromMeta)]
32struct ErrorVariantAttrs {
33    critical: Option<()>,
34    ret_val: Option<Expr>,
35}
36
37struct RetValInfo<'a> {
38    variant_name: &'a Ident,
39    ret_val: Expr,
40}
41
42pub fn generate(attrs: TokenStream, input: TokenStream) -> Result<TokenStream, Error> {
43    let mut enum_item: ItemEnum = parse2(input)?;
44    let ident = enum_item.ident.clone();
45    let enum_attrs = ErrorAttrs::from_list(&syn::parse2::<AttributeArgs>(attrs)?)?;
46
47    let (impl_generics, ty_generics, where_clause) = enum_item.generics.split_for_impl();
48
49    let mut critical_variant = None;
50
51    let mut ret_val_variants = vec![];
52
53    for variant in enum_item.variants.iter_mut() {
54        let variant_name = &variant.ident;
55
56        let (obce_attrs, mut other_attrs) = variant.attrs.iter().cloned().split_attrs()?;
57
58        let variant_attrs = ErrorVariantAttrs::from_list(&obce_attrs)?;
59
60        if variant_attrs.critical.is_some() {
61            other_attrs.push(syn::parse_quote! {
62                #[cfg(feature = "substrate")]
63            });
64
65            let previous_critical_variant = critical_variant.replace(quote! {
66                #[cfg(feature = "substrate")]
67                impl #impl_generics ::obce::substrate::SupportCriticalError for #ident #ty_generics #where_clause {
68                    fn try_to_critical(self) -> Result<::obce::substrate::CriticalError, Self> {
69                        match self {
70                            Self::#variant_name(error) => Ok(error),
71                            _ => Err(self)
72                        }
73                    }
74                }
75            });
76
77            if let Some(variant) = previous_critical_variant {
78                return Err(format_err_spanned!(
79                    variant,
80                    "only one enum variant can be marked as `#[obce(critical)]`",
81                ))
82            }
83        }
84
85        if let Some(ret_val) = variant_attrs.ret_val {
86            ret_val_variants.push(RetValInfo { variant_name, ret_val });
87        } else if enum_attrs.require_ret_val && !ret_val_variants.is_empty() {
88            return Err(format_err_spanned!(
89                variant,
90                "you have to mark this variant with `ret_val` or set `require_ret_val` to `false`"
91            ))
92        }
93
94        variant.attrs = other_attrs;
95    }
96
97    if let Some(expr) = ret_val_variants.iter().map(|info| &info.ret_val).duplicates().next() {
98        return Err(format_err_spanned!(expr, "ret_val value is used twice"))
99    }
100
101    let formatted_ret_val = ret_val_variants.iter().map(|RetValInfo { variant_name, ret_val }| {
102        quote! {
103            #ident::#variant_name => Ok(Self::Converging(#ret_val)),
104        }
105    });
106
107    let ret_val_impl = quote! {
108        impl #impl_generics ::core::convert::TryFrom<#ident #ty_generics>
109            for ::obce::substrate::pallet_contracts::chain_extension::RetVal
110            #where_clause
111        {
112            type Error = #ident #ty_generics;
113
114            fn try_from(value: #ident #ty_generics) -> Result<Self, #ident #ty_generics> {
115                match value {
116                    #(#formatted_ret_val)*
117                    _ => Err(value)
118                }
119            }
120        }
121    };
122
123    Ok(quote! {
124        #[derive(Debug, Copy, Clone, PartialEq, Eq, ::scale::Encode, ::scale::Decode)]
125        #[cfg_attr(feature = "std", derive(::scale_info::TypeInfo))]
126        #enum_item
127
128        #critical_variant
129
130        #[cfg(feature = "substrate")]
131        #ret_val_impl
132    })
133}