sylvia_derive/parser/attributes/
mod.rs

1//! Module defining parsing of Sylvia attributes.
2//! Every Sylvia attribute should be prefixed with `sv::`
3
4use data::DataFieldParams;
5use features::SylviaFeatures;
6use payload::PayloadFieldParam;
7use proc_macro_error::emit_error;
8use syn::spanned::Spanned;
9use syn::{Attribute, MetaList, PathSegment};
10
11pub mod attr;
12pub mod custom;
13pub mod data;
14pub mod error;
15pub mod features;
16pub mod messages;
17pub mod msg;
18pub mod override_entry_point;
19pub mod payload;
20
21pub use attr::{MsgAttrForwarding, VariantAttrForwarding};
22pub use custom::Custom;
23pub use error::ContractErrorAttr;
24pub use messages::{ContractMessageAttr, Customs};
25pub use msg::{MsgAttr, MsgType};
26pub use override_entry_point::{FilteredOverrideEntryPoints, OverrideEntryPoint};
27
28/// This struct represents all possible attributes that
29/// are parsed and utilized by sylvia.
30#[derive(Clone, Copy, Debug, PartialEq)]
31pub enum SylviaAttribute {
32    Custom,
33    Error,
34    Messages,
35    Msg,
36    OverrideEntryPoint,
37    VariantAttrs,
38    MsgAttrs,
39    Payload,
40    Data,
41    Features,
42}
43
44impl SylviaAttribute {
45    pub fn new(attr: &Attribute) -> Option<Self> {
46        let segments = &attr.path().segments;
47        if segments.len() == 2 && segments[0].ident == "sv" {
48            Self::match_attribute(&segments[1])
49        } else {
50            None
51        }
52    }
53
54    fn match_attribute(segment: &PathSegment) -> Option<Self> {
55        match segment.ident.to_string().as_str() {
56            "custom" => Some(Self::Custom),
57            "error" => Some(Self::Error),
58            "messages" => Some(Self::Messages),
59            "msg" => Some(Self::Msg),
60            "override_entry_point" => Some(Self::OverrideEntryPoint),
61            "attr" => Some(Self::VariantAttrs),
62            "msg_attr" => Some(Self::MsgAttrs),
63            "payload" => Some(Self::Payload),
64            "data" => Some(Self::Data),
65            "features" => Some(Self::Features),
66            _ => None,
67        }
68    }
69}
70
71/// The structure parses all attributes provided in `new` method
72/// and stores the one relevant for sylvia.
73#[derive(Default)]
74pub struct ParsedSylviaAttributes {
75    pub custom_attr: Option<Custom>,
76    pub error_attrs: Option<ContractErrorAttr>,
77    pub messages_attrs: Vec<ContractMessageAttr>,
78    pub msg_attr: Option<MsgAttr>,
79    pub override_entry_point_attrs: Vec<OverrideEntryPoint>,
80    pub variant_attrs_forward: Vec<VariantAttrForwarding>,
81    pub msg_attrs_forward: Vec<MsgAttrForwarding>,
82    pub sv_features: SylviaFeatures,
83    pub data: Option<DataFieldParams>,
84    pub payload: Option<PayloadFieldParam>,
85}
86
87impl ParsedSylviaAttributes {
88    pub fn new<'a>(attrs: impl Iterator<Item = &'a Attribute>) -> Self {
89        let mut result = Self::default();
90        for attr in attrs {
91            let sylvia_attr = SylviaAttribute::new(attr);
92            let attr_content = attr.meta.require_list();
93
94            if let (Some(sylvia_attr), Ok(attr)) = (sylvia_attr, &attr_content) {
95                result.match_attribute(&sylvia_attr, attr);
96            } else if sylvia_attr == Some(SylviaAttribute::Data) {
97                // The `sv::data` attribute can be used without parameters.
98                result.data = Some(DataFieldParams::default());
99            } else if sylvia_attr == Some(SylviaAttribute::Payload) {
100                emit_error!(
101                    attr.span(), "Missing parameters for `sv::payload`";
102                    note = "Expected `#[sv::payload(raw)]`"
103                );
104            }
105        }
106
107        if let Some(attr) = result.variant_attrs_forward.first() {
108            let msg_type = result.msg_attr.as_ref().map(MsgAttr::msg_type);
109            if let Some(MsgType::Instantiate) = msg_type {
110                emit_error!(
111                    attr.span, "The attribute `sv::attr` is not supported for `instantiate`";
112                    note = "Message `instantiate` is a structure, use `#[sv::msg_attr] instead`";
113                );
114            } else if let Some(MsgType::Migrate) = msg_type {
115                emit_error!(
116                    attr.span, "The attribute `sv::attr` is not supported for `migrate`";
117                    note = "Message `migrate` is a structure, use `#[sv::msg_attr] instead`";
118                );
119            }
120        }
121
122        result
123    }
124
125    fn match_attribute(&mut self, attribute_type: &SylviaAttribute, attr: &MetaList) {
126        match attribute_type {
127            SylviaAttribute::Custom => {
128                if self.custom_attr.is_none() {
129                    if let Ok(custom_attr) = Custom::new(attr) {
130                        self.custom_attr = Some(custom_attr);
131                    }
132                } else {
133                    emit_error!(
134                        attr, "The attribute `sv::custom` is redefined";
135                        note = attr.span() => "Previous definition of the attribute `sv::custom`";
136                        note = "Only one `sv::custom` attribute can exist on a single sylvia entity"
137                    );
138                }
139            }
140            SylviaAttribute::Error => {
141                if self.error_attrs.is_none() {
142                    if let Ok(error_attr) = ContractErrorAttr::new(attr) {
143                        self.error_attrs = Some(error_attr);
144                    }
145                } else {
146                    emit_error!(
147                        attr, "The attribute `sv::error` is redefined";
148                        note = attr.span() => "Previous definition of the attribute `sv::error`";
149                        note = "Only one `sv::error` attribute can exist on a single method"
150                    );
151                }
152            }
153            SylviaAttribute::Messages => {
154                if let Ok(contract) = ContractMessageAttr::new(attr) {
155                    self.messages_attrs.push(contract);
156                }
157            }
158            SylviaAttribute::Msg => {
159                if self.msg_attr.is_none() {
160                    if let Ok(msg_attr) = MsgAttr::new(attr) {
161                        self.msg_attr = Some(msg_attr);
162                    }
163                } else {
164                    emit_error!(
165                        attr, "The attribute `sv::msg` is redefined";
166                        note = attr.span() => "Previous definition of the attribute `sv::msg`";
167                        note = "Only one `sv::msg` attribute can exist on a single method"
168                    );
169                }
170            }
171            SylviaAttribute::OverrideEntryPoint => {
172                if let Ok(override_entry_point) = OverrideEntryPoint::new(attr) {
173                    self.override_entry_point_attrs.push(override_entry_point)
174                }
175            }
176            SylviaAttribute::VariantAttrs => {
177                self.variant_attrs_forward
178                    .push(VariantAttrForwarding::new(attr));
179            }
180            SylviaAttribute::MsgAttrs => {
181                if let Ok(message_attrs) = MsgAttrForwarding::new(attr) {
182                    self.msg_attrs_forward.push(message_attrs);
183                }
184            }
185            SylviaAttribute::Payload => {
186                if let Ok(payload) = PayloadFieldParam::new(attr) {
187                    self.payload = Some(payload);
188                }
189            }
190            SylviaAttribute::Data => {
191                if let Ok(data) = DataFieldParams::new(attr) {
192                    self.data = Some(data);
193                }
194            }
195            SylviaAttribute::Features => {
196                if let Ok(features) = SylviaFeatures::new(attr) {
197                    self.sv_features = features;
198                }
199            }
200        }
201    }
202}