use proc_macro2::{Ident, Span};
use syn::{spanned::Spanned, Attribute, Error, Fields, Result, Type, TypePath, Variant};
use crate::{
command::user_application::{ApplicationIntegrationType, InteractionContextType},
parse::{
attribute::NamedAttrs,
parsers::{CommandDescription, CommandName, FunctionPath},
syntax::find_attr,
},
};
pub struct ParsedVariant {
pub span: Span,
pub ident: Ident,
pub attribute: VariantAttribute,
pub inner: TypePath,
}
impl ParsedVariant {
pub fn from_variants(
variants: impl IntoIterator<Item = Variant>,
input_span: Span,
) -> Result<Vec<Self>> {
let variants: Vec<_> = variants.into_iter().collect();
if variants.is_empty() {
return Err(Error::new(
input_span,
"enum must have at least one variant",
));
}
variants.into_iter().map(Self::from_variant).collect()
}
fn from_variant(variant: Variant) -> Result<Self> {
let span = variant.span();
let Fields::Unnamed(fields) = variant.fields else {
return Err(Error::new(span, "variant must be an unnamed variant"));
};
if fields.unnamed.len() != 1 {
return Err(Error::new(
span,
"variant must have exactly one unnamed field",
));
}
let inner = match &fields.unnamed[0].ty {
Type::Path(ty) => ty.clone(),
other => {
return Err(Error::new(
other.span(),
"unsupported type, expected a type path",
))
}
};
let attribute = match find_attr(&variant.attrs, "command") {
Some(attr) => VariantAttribute::parse(attr)?,
None => {
return Err(Error::new(
span,
"missing required #[command(..)] attribute",
))
}
};
Ok(Self {
span,
ident: variant.ident,
attribute,
inner,
})
}
}
pub struct VariantAttribute {
pub name: CommandName,
}
impl VariantAttribute {
pub fn parse(attr: &Attribute) -> Result<Self> {
let mut parser = NamedAttrs::parse(attr, &["name"])?;
Ok(Self {
name: parser.required("name")?,
})
}
}
pub struct TypeAttribute {
pub name: CommandName,
pub name_localizations: Option<FunctionPath>,
pub desc: Option<CommandDescription>,
pub desc_localizations: Option<FunctionPath>,
pub default_permissions: Option<FunctionPath>,
pub dm_permission: Option<bool>,
pub nsfw: Option<bool>,
pub contexts: Option<Vec<InteractionContextType>>,
pub integration_types: Option<Vec<ApplicationIntegrationType>>,
}
impl TypeAttribute {
const VALID_ATTRIBUTES: &'static [&'static str] = &[
"name",
"name_localizations",
"desc",
"desc_localizations",
"default_permissions",
"dm_permission",
"nsfw",
"contexts",
"integration_types",
];
pub fn parse(attr: &Attribute) -> Result<Self> {
let mut parser = NamedAttrs::parse(attr, Self::VALID_ATTRIBUTES)?;
Ok(Self {
name: parser.required("name")?,
name_localizations: parser.optional("name_localizations")?,
desc: parser.optional("desc")?,
desc_localizations: parser.optional("desc_localizations")?,
default_permissions: parser.optional("default_permissions")?,
dm_permission: parser.optional("dm_permission")?,
nsfw: parser.optional("nsfw")?,
contexts: parser.optional("contexts")?,
integration_types: parser.optional("integration_types")?,
})
}
}