sylvia_derive/parser/attributes/
messages.rs1use convert_case::{Case, Casing};
2use proc_macro_error::emit_warning;
3use syn::fold::Fold;
4use syn::parse::{Parse, ParseStream, Parser};
5use syn::spanned::Spanned;
6use syn::{parenthesized, Error, Ident, MetaList, Path, Result, Token};
7
8use proc_macro_error::emit_error;
9
10use crate::fold::StripGenerics;
11
12#[derive(Debug)]
14pub struct ContractMessageAttr {
15 pub module: Path,
16 pub variant: Ident,
17 pub customs: Customs,
18}
19
20impl ContractMessageAttr {
21 pub fn new(attr: &MetaList) -> Result<Self> {
22 ContractMessageAttr::parse
23 .parse2(attr.tokens.clone())
24 .map_err(|err| {
25 emit_error!(err.span(), err);
26 err
27 })
28 }
29}
30
31#[derive(Debug)]
37pub struct Customs {
38 pub has_msg: bool,
39 pub has_query: bool,
40}
41
42fn interface_has_custom(content: ParseStream) -> Result<Customs> {
43 let mut customs = Customs {
44 has_msg: false,
45 has_query: false,
46 };
47
48 if !content.peek(Token![:]) {
49 return Ok(customs);
50 }
51
52 let _: Token![:] = content.parse()?;
53 let attr: Ident = content.parse()?;
54 if attr != "custom" {
55 return Ok(customs);
56 }
57
58 let custom_content;
59 parenthesized!(custom_content in content);
60
61 while !custom_content.is_empty() {
62 let custom = custom_content.parse::<Path>()?;
63 match custom.get_ident() {
64 Some(ident) if ident == "msg" => customs.has_msg = true,
65 Some(ident) if ident == "query" => customs.has_query = true,
66 _ => {
67 return Err(Error::new(
68 custom.span(),
69 "Invalid custom attribute, expected one or both of: [`msg`, `query`]\n
70 = note: Expected attribute to be in form `#[sv::messages(interface: custom(msg, query))]`.\n",
71 ))
72 }
73 }
74 if !custom_content.peek(Token![,]) {
75 break;
76 }
77 let _: Token![,] = custom_content.parse()?;
78 }
79 Ok(customs)
80}
81
82impl Parse for ContractMessageAttr {
83 fn parse(input: ParseStream) -> Result<Self> {
84 let module = input.parse()?;
85 let module = StripGenerics.fold_path(module);
86
87 let variant = if input.parse::<Token![as]>().is_ok() {
88 let variant: Ident = input.parse()?;
89 if Some(variant.to_string())
90 == module
91 .segments
92 .last()
93 .map(|name| name.ident.to_string().to_case(Case::UpperCamel))
94 {
95 emit_warning!(
96 variant.span(), "Redundant `as {}`.", variant;
97 note = "Interface name is a camel case version of the path and can be auto deduced.";
98 note = "Attribute can be simplified to: `#[sv::messages(interface_path)]`"
99 )
100 }
101 variant
102 } else if let Some(module_name) = &module.segments.last() {
103 let interface_name = module_name.ident.to_string().to_case(Case::UpperCamel);
104 syn::Ident::new(&interface_name, module.span())
105 } else {
106 Ident::new("", module.span())
107 };
108 let customs = interface_has_custom(input)?;
109 if !input.is_empty() {
110 return Err(Error::new(input.span(),
111 "Unexpected tokens inside `sv::messages` attribtue.\n
112 = note: Maximal supported form of attribute: `#[sv::messages(interface::path as InterfaceName: custom(msg, query))]`.\n"
113 ));
114 }
115 Ok(Self {
116 module,
117 variant,
118 customs,
119 })
120 }
121}