Skip to main content

codama_attributes/codama_directives/
argument_directive.rs

1use crate::{
2    codama_directives::type_nodes::StructFieldMetaConsumer,
3    utils::{FromMeta, MetaConsumer},
4    Attribute, CodamaAttribute, CodamaDirective, Resolvable,
5};
6use codama_errors::{CodamaError, CodamaResult};
7use codama_nodes::{
8    CamelCaseString, DefaultValueStrategy, Docs, InstructionArgumentNode,
9    InstructionInputValueNode, TypeNode,
10};
11use codama_syn_helpers::Meta;
12
13#[derive(Debug, PartialEq)]
14pub struct ArgumentDirective {
15    pub after: bool,
16    pub name: CamelCaseString,
17    pub r#type: Resolvable<TypeNode>,
18    pub docs: Docs,
19    pub default_value: Option<Resolvable<InstructionInputValueNode>>,
20    pub default_value_strategy: Option<DefaultValueStrategy>,
21}
22
23impl ArgumentDirective {
24    pub fn parse(meta: &Meta) -> syn::Result<Self> {
25        meta.assert_directive("argument")?;
26        let consumer = StructFieldMetaConsumer::from_meta(meta)?
27            .consume_field()?
28            .consume_argument_default_value()?
29            .consume_after()?
30            .assert_fully_consumed()?;
31
32        let default_value = consumer.default_instruction_input_value_node();
33        let default_value_strategy = consumer.default_value_strategy();
34
35        Ok(ArgumentDirective {
36            after: consumer.after.option().unwrap_or(false),
37            name: consumer.name.take(meta)?,
38            r#type: consumer.r#type.take(meta)?,
39            docs: consumer.docs.option().unwrap_or_default(),
40            default_value,
41            default_value_strategy,
42        })
43    }
44
45    /// Construct an `InstructionArgumentNode` from this directive.
46    /// Returns an error if any unresolved directives remain.
47    pub fn to_instruction_argument_node(&self) -> CodamaResult<InstructionArgumentNode> {
48        Ok(InstructionArgumentNode {
49            name: self.name.clone(),
50            r#type: self.r#type.try_resolved()?.clone(),
51            docs: self.docs.clone(),
52            default_value: self
53                .default_value
54                .as_ref()
55                .map(|r| r.try_resolved().cloned())
56                .transpose()?,
57            default_value_strategy: self.default_value_strategy,
58        })
59    }
60}
61
62impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ArgumentDirective {
63    type Error = CodamaError;
64
65    fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
66        match attribute.directive.as_ref() {
67            CodamaDirective::Argument(ref a) => Ok(a),
68            _ => Err(CodamaError::InvalidCodamaDirective {
69                expected: "argument".to_string(),
70                actual: attribute.directive.name().to_string(),
71            }),
72        }
73    }
74}
75
76impl<'a> TryFrom<&'a Attribute<'a>> for &'a ArgumentDirective {
77    type Error = CodamaError;
78
79    fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
80        <&CodamaAttribute>::try_from(attribute)?.try_into()
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use codama_nodes::{NumberFormat::U8, NumberTypeNode, PayerValueNode};
88
89    #[test]
90    fn ok() {
91        let meta: Meta = syn::parse_quote! { argument("age", number(u8)) };
92        let directive = ArgumentDirective::parse(&meta).unwrap();
93        assert_eq!(
94            directive,
95            ArgumentDirective {
96                after: false,
97                name: "age".into(),
98                r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
99                docs: Docs::default(),
100                default_value: None,
101                default_value_strategy: None,
102            }
103        );
104    }
105
106    #[test]
107    fn after() {
108        let meta: Meta = syn::parse_quote! { argument(after, "age", number(u8)) };
109        let directive = ArgumentDirective::parse(&meta).unwrap();
110        assert_eq!(
111            directive,
112            ArgumentDirective {
113                after: true,
114                name: "age".into(),
115                r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
116                docs: Docs::default(),
117                default_value: None,
118                default_value_strategy: None,
119            }
120        );
121    }
122
123    #[test]
124    fn with_default_value() {
125        let meta: Meta = syn::parse_quote! { argument("age", number(u8), default_value = payer) };
126        let directive = ArgumentDirective::parse(&meta).unwrap();
127        assert_eq!(
128            directive,
129            ArgumentDirective {
130                after: false,
131                name: "age".into(),
132                r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
133                docs: Docs::default(),
134                default_value: Some(Resolvable::Resolved(PayerValueNode::new().into())),
135                default_value_strategy: None,
136            }
137        );
138    }
139
140    #[test]
141    fn with_docs_string() {
142        let meta: Meta = syn::parse_quote! { argument("cake", number(u8), docs = "The cake") };
143        let directive = ArgumentDirective::parse(&meta).unwrap();
144        assert_eq!(directive.docs, vec!["The cake".to_string()].into());
145    }
146
147    #[test]
148    fn with_docs_array() {
149        let meta: Meta = syn::parse_quote! { argument("cake", number(u8), docs = ["The cake", "must be a lie"]) };
150        let directive = ArgumentDirective::parse(&meta).unwrap();
151        assert_eq!(
152            directive.docs,
153            vec!["The cake".to_string(), "must be a lie".to_string()].into()
154        );
155    }
156}