codama_attributes/codama_directives/
argument_directive.rs1use 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 pub fn to_instruction_argument_node(&self) -> CodamaResult<InstructionArgumentNode> {
48 Ok(InstructionArgumentNode {
49 name: self.name.clone(),
50 r#type: Box::new(self.r#type.try_resolved()?.clone()),
51 docs: self.docs.clone(),
52 default_value: Box::new(
53 self.default_value
54 .as_ref()
55 .map(|r| r.try_resolved().cloned())
56 .transpose()?,
57 ),
58 default_value_strategy: self.default_value_strategy,
59 display: None,
60 })
61 }
62}
63
64impl<'a> TryFrom<&'a CodamaAttribute<'a>> for &'a ArgumentDirective {
65 type Error = CodamaError;
66
67 fn try_from(attribute: &'a CodamaAttribute) -> Result<Self, Self::Error> {
68 match attribute.directive.as_ref() {
69 CodamaDirective::Argument(ref a) => Ok(a),
70 _ => Err(CodamaError::InvalidCodamaDirective {
71 expected: "argument".to_string(),
72 actual: attribute.directive.name().to_string(),
73 }),
74 }
75 }
76}
77
78impl<'a> TryFrom<&'a Attribute<'a>> for &'a ArgumentDirective {
79 type Error = CodamaError;
80
81 fn try_from(attribute: &'a Attribute) -> Result<Self, Self::Error> {
82 <&CodamaAttribute>::try_from(attribute)?.try_into()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use codama_nodes::{NumberFormat::U8, NumberTypeNode, PayerValueNode};
90
91 #[test]
92 fn ok() {
93 let meta: Meta = syn::parse_quote! { argument("age", number(u8)) };
94 let directive = ArgumentDirective::parse(&meta).unwrap();
95 assert_eq!(
96 directive,
97 ArgumentDirective {
98 after: false,
99 name: "age".into(),
100 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
101 docs: Docs::default(),
102 default_value: None,
103 default_value_strategy: None,
104 }
105 );
106 }
107
108 #[test]
109 fn after() {
110 let meta: Meta = syn::parse_quote! { argument(after, "age", number(u8)) };
111 let directive = ArgumentDirective::parse(&meta).unwrap();
112 assert_eq!(
113 directive,
114 ArgumentDirective {
115 after: true,
116 name: "age".into(),
117 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
118 docs: Docs::default(),
119 default_value: None,
120 default_value_strategy: None,
121 }
122 );
123 }
124
125 #[test]
126 fn with_default_value() {
127 let meta: Meta = syn::parse_quote! { argument("age", number(u8), default_value = payer) };
128 let directive = ArgumentDirective::parse(&meta).unwrap();
129 assert_eq!(
130 directive,
131 ArgumentDirective {
132 after: false,
133 name: "age".into(),
134 r#type: Resolvable::Resolved(NumberTypeNode::le(U8).into()),
135 docs: Docs::default(),
136 default_value: Some(Resolvable::Resolved(PayerValueNode::new().into())),
137 default_value_strategy: None,
138 }
139 );
140 }
141
142 #[test]
143 fn with_docs_string() {
144 let meta: Meta = syn::parse_quote! { argument("cake", number(u8), docs = "The cake") };
145 let directive = ArgumentDirective::parse(&meta).unwrap();
146 assert_eq!(directive.docs, vec!["The cake".to_string()].into());
147 }
148
149 #[test]
150 fn with_docs_array() {
151 let meta: Meta = syn::parse_quote! { argument("cake", number(u8), docs = ["The cake", "must be a lie"]) };
152 let directive = ArgumentDirective::parse(&meta).unwrap();
153 assert_eq!(
154 directive.docs,
155 vec!["The cake".to_string(), "must be a lie".to_string()].into()
156 );
157 }
158}