use syn::{
Expr, ExprCall, ExprPath, Lit, Result as SynResult, Token,
parse::{Parse, ParseStream},
};
#[derive(Debug, Clone)]
pub struct ParsedProperty {
pub tag: String,
pub args: Vec<Expr>,
pub kind: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct ParsedRapxAttr {
pub properties: Vec<ParsedProperty>,
}
impl Parse for ParsedProperty {
fn parse(input: ParseStream<'_>) -> SynResult<Self> {
let expr: Expr = input.parse()?;
let mut property = parse_property_expr(expr)?;
if input.peek(Token![,]) {
let fork = input.fork();
let _: Token![,] = fork.parse()?;
if fork.peek(syn::Ident) && fork.peek2(Token![=]) {
let _: Token![,] = input.parse()?;
let ident: syn::Ident = input.parse()?;
let _: Token![=] = input.parse()?;
let value: Expr = input.parse()?;
if ident == "kind" {
if let Expr::Lit(ref expr_lit) = value
&& let Lit::Str(ref kind) = expr_lit.lit
{
property.kind = Some(kind.value());
} else {
return Err(syn::Error::new_spanned(
value,
"RAPx requires attribute kind must be a string literal",
));
}
} else {
return Err(syn::Error::new(
ident.span(),
"unsupported named RAPx requires attribute argument",
));
}
}
}
Ok(property)
}
}
struct RequireOuterAttribute {
attr: syn::Attribute,
}
impl Parse for RequireOuterAttribute {
fn parse(input: ParseStream<'_>) -> SynResult<Self> {
Ok(Self {
attr: input
.call(syn::Attribute::parse_outer)?
.into_iter()
.next()
.ok_or_else(|| input.error("expected exactly one outer attribute"))?,
})
}
}
pub fn parse_rapx_attr(attr_str: &str, expected_name: &str) -> SynResult<ParsedRapxAttr> {
let attr = syn::parse_str::<RequireOuterAttribute>(attr_str)?.attr;
if !is_expected_syn_rapx_attr(&attr, expected_name) {
return Ok(ParsedRapxAttr::default());
}
let syn::Meta::List(meta_list) = &attr.meta else {
return Ok(ParsedRapxAttr::default());
};
let property = meta_list.parse_args::<ParsedProperty>()?;
Ok(ParsedRapxAttr {
properties: vec![property],
})
}
fn is_expected_syn_rapx_attr(attr: &syn::Attribute, expected_name: &str) -> bool {
let mut segments = attr.path().segments.iter();
matches!(
(segments.next(), segments.next(), segments.next()),
(Some(first), Some(second), None)
if first.ident == "rapx" && second.ident == expected_name
)
}
fn parse_property_expr(expr: Expr) -> SynResult<ParsedProperty> {
match expr {
Expr::Call(ExprCall { func, args, .. }) => {
let tag = match *func {
Expr::Path(ExprPath { path, .. }) => path
.segments
.last()
.map(|seg| seg.ident.to_string())
.ok_or_else(|| syn::Error::new_spanned(path, "missing property name"))?,
other => {
return Err(syn::Error::new_spanned(
other,
"unsupported RAPx property callee expression",
));
}
};
Ok(ParsedProperty {
tag,
args: args.into_iter().collect(),
kind: None,
})
}
other => Err(syn::Error::new_spanned(
other,
"unsupported RAPx property expression",
)),
}
}