1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
use syn::{Attribute, Meta, NestedMeta, MetaList, Ident, parse_quote};
use proc_macro2::Span;

use crate::err::ErrorSink;

/// Name of the auto-generated struct that represents a contract or a module.
pub const CONTRACT: &str = "Contract";
/// Name of the auto-generated enum that aggregates
/// all error types that a contract might have.
pub const ERROR_ENUM: &str = "Error";
/// The [`ERROR_ENUM`] enum variant case that represents an error
/// returned by a method implemented on the current contract and
/// not one coming from an interface.
pub const CONTRACT_ERR_VARIANT: &str = "Base";
/// The [`ERROR_ENUM`] enum variant case that represents an error
/// when trying to convert a query response to binary.
pub const BINARY_SERIALIZE_ERR_VARIANT: &str = "QueryResponseSerialize";

pub const INIT_MSG: &str = "InstantiateMsg";
pub const EXECUTE_MSG: &str = "ExecuteMsg";
pub const QUERY_MSG: &str = "QueryMsg";

pub const INIT_FN: &str = "instantiate";
pub const EXECUTE_FN: &str = "execute";
pub const QUERY_FN: &str = "query";

/// Name of the associated type that represents the error type in an interface.
pub const ERROR_TYPE: &str = "Error";

#[derive(Clone, Copy, Debug)]
pub enum MsgAttr {
    Init { entry: Option<Entry> },
    Execute,
    Query,
    Reply,
    ExecuteGuard
}

#[derive(Clone, Copy, Debug)]
pub enum Entry {
    Functions,
    Wasm
}

impl MsgAttr {
    /// Used as a meta tag in the `#[init(entry)]` attribute.
    pub const ENTRY_META: &str = "entry";
    /// Used as a meta tag in the `#[init(entry_wasm)]` attribute.
    pub const ENTRY_WASM_META: &str = "entry_wasm";

    pub const INIT: &str = "init";
    pub const EXECUTE: &str = "execute";
    pub const QUERY: &str = "query";
    pub const REPLY: &str = "reply";
    pub const EXECUTE_GUARD: &str = "execute_guard";

    pub fn parse(sink: &mut ErrorSink, attrs: &[Attribute]) -> Option<Self> {
        for attr in attrs {
            if let Some(ident) = attr.path.get_ident() {
                let meta = match attr.parse_meta() {
                    Ok(meta) => meta,
                    Err(err) => {
                        sink.push_err(err);

                        continue;
                    }
                };

                let instance = match ident.to_string().as_str() {
                    Self::INIT => {
                        let mut entry = None;

                        if let Meta::List(list) = meta {
                            entry = validate_entry_meta(sink, &list);
                        } else {
                            assert_is_path_ident(sink, &meta);
                        }

                        Some(Self::Init { entry })
                    },
                    Self::EXECUTE => {
                        assert_is_path_ident(sink, &meta);

                        Some(Self::Execute)
                    }
                    Self::QUERY => {
                        assert_is_path_ident(sink, &meta);

                        Some(Self::Query)
                    },
                    Self::EXECUTE_GUARD => {
                        assert_is_path_ident(sink, &meta);

                        Some(Self::ExecuteGuard)
                    }
                    Self::REPLY => {
                        assert_is_path_ident(sink, &meta);

                        Some(Self::Reply)
                    }
                    _ => None
                };

                if instance.is_some() {
                    return instance;
                }
            }
        }
    
        None
    }

    #[inline]
    pub const fn as_str(&self) -> &'static str {
        match self {
            MsgAttr::Init { .. } => Self::INIT,
            MsgAttr::Execute => Self::EXECUTE,
            MsgAttr::Query => Self::QUERY,
            MsgAttr::Reply => Self::REPLY,
            MsgAttr::ExecuteGuard => Self::EXECUTE_GUARD
        }
    }
}

fn validate_entry_meta(sink: &mut ErrorSink, list: &MetaList) -> Option<Entry> {
    if list.nested.len() == 1 {
        let entry = Ident::new(MsgAttr::ENTRY_META, Span::call_site());
        let entry_wasm = Ident::new(MsgAttr::ENTRY_WASM_META, Span::call_site());

        let expected: [(NestedMeta, Entry); 2] = [
            (parse_quote!(#entry), Entry::Functions),
            (parse_quote!(#entry_wasm), Entry::Wasm)
        ];

        let first = &list.nested[0];

        if let Some(index) = expected.iter().position(|x| x.0 == *first) {
            return Some(expected[index].1);
        }
    }

    sink.push_spanned(
        list, 
        format!(
            "Expecting one of nested meta: \"{:?}\".",
            [MsgAttr::ENTRY_META, MsgAttr::ENTRY_WASM_META]
        )
    );

    None
}

#[inline]
fn assert_is_path_ident(sink: &mut ErrorSink, meta: &Meta) {
    if !matches!(meta, Meta::Path(path) if path.segments.len() == 1) {
        sink.push_spanned(meta, "Unexpected meta.");
    }
}