stellar-axelar-std-derive 2.1.0

Proc macros for Axelar contracts.
Documentation
use proc_macro2::Ident;
use syn::punctuated::Punctuated;
use syn::token::Comma;
use syn::{FnArg, ImplItemFn, ItemFn, Pat, PatType, Stmt, Type, TypePath};

pub trait PrependStatement {
    fn prepend_statement(&mut self, stmt: Stmt) -> &Self;
}

impl PrependStatement for ItemFn {
    fn prepend_statement(&mut self, stmt: Stmt) -> &Self {
        self.block.stmts.insert(0, stmt);
        self
    }
}

impl PrependStatement for ImplItemFn {
    fn prepend_statement(&mut self, stmt: Stmt) -> &Self {
        self.block.stmts.insert(0, stmt);
        self
    }
}

pub fn parse_env_identifier(args: &Punctuated<FnArg, Comma>) -> Result<&Ident, syn::Error> {
    args.iter()
        .filter_map(match_arg_type_pattern)
        .filter(|type_pattern| is_env_type(&type_pattern.ty))
        .find_map(|pat_type| match_env_arg_identifier(&pat_type.pat))
        .ok_or_else(|| {
            syn::Error::new_spanned(
                args,
                "non-empty contract endpoints must have an Env argument",
            )
        })
}

const fn match_arg_type_pattern(arg: &FnArg) -> Option<&PatType> {
    match arg {
        FnArg::Typed(pat_type) => Some(pat_type),
        _ => None,
    }
}

fn is_env_type(ty: &Type) -> bool {
    match ty {
        Type::Path(TypePath { path, .. }) => path
            .segments
            .last()
            .is_some_and(|segment| segment.ident == "Env"),
        Type::Reference(reference) => is_env_type(&reference.elem),
        _ => false,
    }
}

const fn match_env_arg_identifier(pat: &Pat) -> Option<&Ident> {
    match pat {
        Pat::Ident(pat) => Some(&pat.ident),
        _ => None,
    }
}

#[cfg(test)]
mod tests {
    use quote::ToTokens;
    use syn::parse_quote;

    use crate::utils::{parse_env_identifier, PrependStatement};

    #[test]
    fn prepend_statement_empty_body_generation_succeeds() {
        let mut input_fn: syn::ItemFn = syn::parse_quote! {
            fn test_fn(env: &Env, other: i32) {}
        };

        let generated_function = input_fn
            .prepend_statement(parse_quote! {
                Self::operator(&env).require_auth();
            })
            .to_token_stream();
        let generated_function_file: syn::File = syn::parse2(generated_function).unwrap();
        let formatted_generated_function = prettyplease::unparse(&generated_function_file);
        goldie::assert!(formatted_generated_function);
    }

    #[test]
    fn prepend_statement_generation_succeeds() {
        let mut input_fn: syn::ItemFn = syn::parse_quote! {
            fn test_fn(env: &Env, other: i32) {
            let x = 42;
            let y = vec![1, 2, 3];
            let z = x + y[2];
            }
        };

        let generated_function = input_fn
            .prepend_statement(parse_quote! {
                Self::operator(&env).require_auth();
            })
            .to_token_stream();

        let generated_function_file: syn::File = syn::parse2(generated_function).unwrap();
        let formatted_generated_function = prettyplease::unparse(&generated_function_file);

        goldie::assert!(formatted_generated_function);
    }

    #[test]
    fn function_with_args_but_no_env() {
        let args = syn::parse_quote! { something:String, other: i32 };

        assert!(parse_env_identifier(&args).is_err());
    }

    #[test]
    fn function_with_args_and_env() {
        let args = syn::parse_quote! { something:String, other: i32, some_env: Env};

        assert!(parse_env_identifier(&args).is_ok());
    }
}