rapx/verify/contract/
attr.rs1use syn::{
16 Expr, ExprCall, ExprPath, Lit, Result as SynResult, Token,
17 parse::{Parse, ParseStream},
18};
19
20use regex::Regex;
21use std::sync::LazyLock;
22
23#[derive(Debug, Clone)]
25pub struct ParsedProperty {
26 pub tag: String,
28 pub args: Vec<Expr>,
30 pub kind: Option<String>,
32}
33
34impl Parse for ParsedProperty {
35 fn parse(input: ParseStream<'_>) -> SynResult<Self> {
41 let expr: Expr = input.parse()?;
42 let mut property = parse_property_expr(expr)?;
43
44 if input.peek(Token![,]) {
45 let fork = input.fork();
46 let _: Token![,] = fork.parse()?;
47 if fork.peek(syn::Ident) && fork.peek2(Token![=]) {
48 let _: Token![,] = input.parse()?;
49 let ident: syn::Ident = input.parse()?;
50 let _: Token![=] = input.parse()?;
51 let value: Expr = input.parse()?;
52
53 if ident == "kind" {
54 if let Expr::Lit(ref expr_lit) = value
55 && let Lit::Str(ref kind) = expr_lit.lit
56 {
57 property.kind = Some(kind.value());
58 } else {
59 return Err(syn::Error::new_spanned(
60 value,
61 "RAPx requires attribute kind must be a string literal",
62 ));
63 }
64 } else {
65 return Err(syn::Error::new(
66 ident.span(),
67 "unsupported named RAPx requires attribute argument",
68 ));
69 }
70 }
71 }
72
73 Ok(property)
74 }
75}
76
77struct RequireOuterAttribute {
79 attr: syn::Attribute,
80}
81
82impl Parse for RequireOuterAttribute {
83 fn parse(input: ParseStream<'_>) -> SynResult<Self> {
85 Ok(Self {
86 attr: input
87 .call(syn::Attribute::parse_outer)?
88 .into_iter()
89 .next()
90 .ok_or_else(|| input.error("expected exactly one outer attribute"))?,
91 })
92 }
93}
94
95pub fn parse_rapx_attr(
100 attr_str: &str,
101 expected_name: &str,
102) -> SynResult<Option<ParsedProperty>> {
103 let attr_str = strip_lifetime_ticks(attr_str);
104 let attr = syn::parse_str::<RequireOuterAttribute>(&attr_str)?.attr;
106 if !is_expected_syn_rapx_attr(&attr, expected_name) {
107 return Ok(None);
108 }
109
110 let syn::Meta::List(meta_list) = &attr.meta else {
112 return Ok(None);
113 };
114
115 let property = meta_list.parse_args::<ParsedProperty>()?;
116 Ok(Some(property))
117}
118
119fn is_expected_syn_rapx_attr(attr: &syn::Attribute, expected_name: &str) -> bool {
121 let mut segments = attr.path().segments.iter();
122 matches!(
123 (segments.next(), segments.next(), segments.next()),
124 (Some(first), Some(second), None)
125 if first.ident == "rapx" && second.ident == expected_name
126 )
127}
128
129fn parse_property_expr(expr: Expr) -> SynResult<ParsedProperty> {
131 match expr {
132 Expr::Call(ExprCall { func, args, .. }) => {
133 let tag = match *func {
135 Expr::Path(ExprPath { path, .. }) => path
136 .segments
137 .last()
138 .map(|seg| seg.ident.to_string())
139 .ok_or_else(|| syn::Error::new_spanned(path, "missing property name"))?,
140 other => {
141 return Err(syn::Error::new_spanned(
142 other,
143 "unsupported RAPx property callee expression",
144 ));
145 }
146 };
147
148 Ok(ParsedProperty {
149 tag,
150 args: args.into_iter().collect(),
151 kind: None,
152 })
153 }
154 other => Err(syn::Error::new_spanned(
155 other,
156 "unsupported RAPx property expression",
157 )),
158 }
159}
160
161static LIFETIME_TICK_RE: LazyLock<Regex> =
165 LazyLock::new(|| Regex::new(r"'([a-zA-Z_][a-zA-Z0-9_]*)").unwrap());
166
167fn strip_lifetime_ticks(s: &str) -> String {
168 LIFETIME_TICK_RE.replace_all(s, "$1").to_string()
169}