zephyrus-macros 0.11.0

Procedural macros used by Zephyrus
Documentation
use darling::FromAttributes;
use proc_macro2::{Ident, TokenStream as TokenStream2};
use syn::{spanned::Spanned, DeriveInput, Error, Result};
use crate::extractors::{Either, FixedList};

#[derive(FromAttributes)]
#[darling(attributes(parse))]
struct VariantAttributes {
    #[darling(rename = "rename")]
    renaming: Option<Either<String, FixedList<1, String>>>,
}

struct Variant {
    value: String,
    ident: Ident,
    index: usize,
}

impl Variant {
    fn parse_tokens(&self, tokens: &mut TokenStream2) {
        let index = &self.index;
        let ident = &self.ident;
        tokens.extend(quote::quote! {
            #index => Ok(Self::#ident),
        })
    }

    fn choice_tokens(&self, tokens: &mut TokenStream2) {
        let value = &self.value;
        let index = self.index as i64;
        tokens.extend(quote::quote! {
            choices.push(CommandOptionChoice {
                    name: #value.to_string(),
                    name_localizations: None,
                    value: CommandOptionChoiceValue::Integer(#index)
                }
            );
        })
    }
}

pub fn parse(input: TokenStream2) -> Result<TokenStream2> {
    let derive = syn::parse2::<DeriveInput>(input)?;
    let enumeration = match derive.data {
        syn::Data::Enum(e) => e,
        _ => {
            return Err(Error::new(
                derive.ident.span(),
                "This derive is only available for enums",
            ))
        }
    };

    let mut variants = Vec::new();
    let mut index = 1;

    for variant in enumeration.variants {
        if !matches!(&variant.fields, syn::Fields::Unit) {
            return Err(Error::new(
                variant.span(),
                "Choice parameter cannot have inner values",
            ));
        }

        let attributes = VariantAttributes::from_attributes(variant.attrs.as_slice())?;

        let name = attributes.renaming
            .map(|item| item.inner().clone())
            .unwrap_or(variant.ident.to_string());

        variants.push(Variant {
            ident: variant.ident.clone(),
            value: name,
            index,
        });

        index += 1;
    }

    let mut parse_stream = TokenStream2::new();
    let mut choice_stream = TokenStream2::new();
    for variant in variants {
        variant.parse_tokens(&mut parse_stream);
        variant.choice_tokens(&mut choice_stream);
    }

    let enum_name = &derive.ident;

    Ok(quote::quote! {
        const _: () = {
            use ::zephyrus::{
                builder::WrappedClient,
                prelude::async_trait,
                parse::{Parse, ParseError},
                twilight_exports::{
                    CommandInteractionDataResolved,
                    CommandOptionChoice,
                    CommandOptionChoiceValue,
                    CommandOptionType,
                    CommandOptionValue,

                },
            };

            #[automatically_derived]
            #[async_trait]
            impl<T: Send + Sync + 'static> Parse<T> for #enum_name {
                async fn parse(
                    http_client: &WrappedClient,
                    data: &T,
                    value: Option<&CommandOptionValue>,
                    resolved: Option<&mut CommandInteractionDataResolved>
                ) -> Result<Self, ParseError>
                {
                    let num = usize::parse(http_client, data, value, resolved).await?;
                    match num {
                        #parse_stream
                        _ => return Err(ParseError::Parsing {
                                argument_name: String::new(),
                                required: true,
                                argument_type: String::from(stringify!(#enum_name)),
                                error: String::from("Unrecognized option")
                            }
                        )
                    }
                }
                fn kind() -> CommandOptionType {
                    CommandOptionType::Integer
                }
                fn choices() -> Option<Vec<CommandOptionChoice>> {
                    let mut choices = Vec::new();

                    #choice_stream;

                    Some(choices)
                }
            }
        };
    })
}